mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-04-20 09:58:28 +00:00
Started mosaic display for conferences
This commit is contained in:
parent
46dc3b2d00
commit
17a4e546a5
9 changed files with 478 additions and 7 deletions
|
|
@ -25,6 +25,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 com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import org.linphone.databinding.CallActiveConferenceFragmentBinding
|
import org.linphone.databinding.CallActiveConferenceFragmentBinding
|
||||||
import org.linphone.ui.call.viewmodel.CallsViewModel
|
import org.linphone.ui.call.viewmodel.CallsViewModel
|
||||||
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
|
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
|
||||||
|
|
@ -66,9 +67,23 @@ class ActiveConferenceCallFragment : GenericCallFragment() {
|
||||||
binding.callsViewModel = callsViewModel
|
binding.callsViewModel = callsViewModel
|
||||||
binding.numpadModel = callViewModel.numpadModel
|
binding.numpadModel = callViewModel.numpadModel
|
||||||
|
|
||||||
|
val actionsBottomSheetBehavior = BottomSheetBehavior.from(binding.bottomBar.root)
|
||||||
|
actionsBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
|
|
||||||
callViewModel.callDuration.observe(viewLifecycleOwner) { duration ->
|
callViewModel.callDuration.observe(viewLifecycleOwner) { duration ->
|
||||||
binding.chronometer.base = SystemClock.elapsedRealtime() - (1000 * duration)
|
binding.chronometer.base = SystemClock.elapsedRealtime() - (1000 * duration)
|
||||||
binding.chronometer.start()
|
binding.chronometer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callViewModel.toggleExtraActionsBottomSheetEvent.observe(viewLifecycleOwner) {
|
||||||
|
it.consume {
|
||||||
|
val state = actionsBottomSheetBehavior.state
|
||||||
|
if (state == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||||
|
actionsBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
} else if (state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||||
|
actionsBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@ import androidx.annotation.WorkerThread
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
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.Participant
|
||||||
|
import org.linphone.core.ParticipantDevice
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
|
|
||||||
class ConferenceModel {
|
class ConferenceModel {
|
||||||
|
|
@ -32,16 +35,152 @@ class ConferenceModel {
|
||||||
|
|
||||||
val subject = MutableLiveData<String>()
|
val subject = MutableLiveData<String>()
|
||||||
|
|
||||||
|
val participantDevices = MutableLiveData<ArrayList<ConferenceParticipantDeviceModel>>()
|
||||||
|
|
||||||
private lateinit var conference: Conference
|
private lateinit var conference: Conference
|
||||||
|
|
||||||
|
private val conferenceListener = object : ConferenceListenerStub() {
|
||||||
|
@WorkerThread
|
||||||
|
override fun onParticipantDeviceAdded(
|
||||||
|
conference: Conference,
|
||||||
|
participantDevice: ParticipantDevice
|
||||||
|
) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Participant device added: ${participantDevice.address.asStringUriOnly()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
val list = arrayListOf<ConferenceParticipantDeviceModel>()
|
||||||
|
list.addAll(participantDevices.value.orEmpty())
|
||||||
|
|
||||||
|
val newModel = ConferenceParticipantDeviceModel(participantDevice)
|
||||||
|
list.add(newModel)
|
||||||
|
|
||||||
|
participantDevices.postValue(sortParticipantDevicesList(list))
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun onParticipantDeviceRemoved(
|
||||||
|
conference: Conference,
|
||||||
|
participantDevice: ParticipantDevice
|
||||||
|
) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Participant device removed: ${participantDevice.address.asStringUriOnly()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
override fun onParticipantDeviceStateChanged(
|
||||||
|
conference: Conference,
|
||||||
|
device: ParticipantDevice,
|
||||||
|
state: ParticipantDevice.State
|
||||||
|
) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Participant device [${device.address.asStringUriOnly()}] state changed [$state]"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun onStateChanged(conference: Conference, state: Conference.State) {
|
||||||
|
Log.i("$TAG State changed [$state]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun destroy() {
|
||||||
|
if (::conference.isInitialized) {
|
||||||
|
conference.removeListener(conferenceListener)
|
||||||
|
participantDevices.value.orEmpty().forEach(ConferenceParticipantDeviceModel::destroy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun configureFromCall(call: Call) {
|
fun configureFromCall(call: Call) {
|
||||||
val conf = call.conference ?: return
|
val conf = call.conference ?: return
|
||||||
|
if (::conference.isInitialized) {
|
||||||
|
conference.removeListener(conferenceListener)
|
||||||
|
}
|
||||||
|
|
||||||
conference = conf
|
conference = conf
|
||||||
|
conference.addListener(conferenceListener)
|
||||||
|
|
||||||
Log.i(
|
Log.i(
|
||||||
"$TAG Configuring conference with subject [${conference.subject}] from call [${call.callLog.callId}]"
|
"$TAG Configuring conference with subject [${conference.subject}] from call [${call.callLog.callId}]"
|
||||||
)
|
)
|
||||||
subject.postValue(conference.subject)
|
subject.postValue(conference.subject)
|
||||||
|
|
||||||
|
computeParticipantsDevices()
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun computeParticipantsDevices() {
|
||||||
|
participantDevices.value.orEmpty().forEach(ConferenceParticipantDeviceModel::destroy)
|
||||||
|
val list = arrayListOf<ConferenceParticipantDeviceModel>()
|
||||||
|
|
||||||
|
val participants = conference.participantList
|
||||||
|
Log.i("$TAG [${participants.size}] participant in conference")
|
||||||
|
|
||||||
|
for (participant in participants) {
|
||||||
|
val devices = participant.devices
|
||||||
|
val role = participant.role
|
||||||
|
|
||||||
|
Log.i(
|
||||||
|
"$TAG Participant [${participant.address.asStringUriOnly()}] has [${devices.size}] devices and role [${role.name}]"
|
||||||
|
)
|
||||||
|
if (role == Participant.Role.Listener) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (device in participant.devices) {
|
||||||
|
val model = ConferenceParticipantDeviceModel(device)
|
||||||
|
list.add(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(
|
||||||
|
"$TAG [${list.size}] participant devices will be displayed (not counting ourselves)"
|
||||||
|
)
|
||||||
|
|
||||||
|
val ourDevices = conference.me.devices
|
||||||
|
Log.i("$TAG We have [${ourDevices.size}] devices")
|
||||||
|
for (device in ourDevices) {
|
||||||
|
val model = ConferenceParticipantDeviceModel(device, true)
|
||||||
|
list.add(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
participantDevices.postValue(sortParticipantDevicesList(list))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sortParticipantDevicesList(devices: List<ConferenceParticipantDeviceModel>): ArrayList<ConferenceParticipantDeviceModel> {
|
||||||
|
val sortedList = arrayListOf<ConferenceParticipantDeviceModel>()
|
||||||
|
sortedList.addAll(devices)
|
||||||
|
|
||||||
|
val meDeviceData = sortedList.find {
|
||||||
|
it.isMe
|
||||||
|
}
|
||||||
|
if (meDeviceData != null) {
|
||||||
|
val index = sortedList.indexOf(meDeviceData)
|
||||||
|
val expectedIndex = sortedList.size - 1
|
||||||
|
if (index != expectedIndex) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Me device data is at index $index, moving it to index $expectedIndex"
|
||||||
|
)
|
||||||
|
sortedList.removeAt(index)
|
||||||
|
sortedList.add(expectedIndex, meDeviceData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* 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.ParticipantDevice
|
||||||
|
import org.linphone.core.ParticipantDeviceListenerStub
|
||||||
|
import org.linphone.core.tools.Log
|
||||||
|
|
||||||
|
class ConferenceParticipantDeviceModel @WorkerThread constructor(
|
||||||
|
val device: ParticipantDevice,
|
||||||
|
val isMe: Boolean = false
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "[Conference Participant Device Model]"
|
||||||
|
}
|
||||||
|
|
||||||
|
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(device.address)
|
||||||
|
|
||||||
|
val isMuted = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val isSpeaking = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
private val deviceListener = object : ParticipantDeviceListenerStub() {
|
||||||
|
override fun onStateChanged(
|
||||||
|
participantDevice: ParticipantDevice,
|
||||||
|
state: ParticipantDevice.State?
|
||||||
|
) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Participant device [${participantDevice.address.asStringUriOnly()}] state changed [$state]"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIsMuted(participantDevice: ParticipantDevice, muted: Boolean) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Participant device [${participantDevice.address.asStringUriOnly()}] is ${if (participantDevice.isMuted) "muted" else "no longer muted"}"
|
||||||
|
)
|
||||||
|
isMuted.postValue(participantDevice.isMuted)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIsSpeakingChanged(
|
||||||
|
participantDevice: ParticipantDevice,
|
||||||
|
speaking: Boolean
|
||||||
|
) {
|
||||||
|
Log.i(
|
||||||
|
"$TAG Participant device [${participantDevice.address.asStringUriOnly()}] is ${if (participantDevice.isSpeaking) "speaking" else "no longer speaking"}"
|
||||||
|
)
|
||||||
|
isSpeaking.postValue(participantDevice.isSpeaking)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
device.addListener(deviceListener)
|
||||||
|
|
||||||
|
isMuted.postValue(device.isMuted)
|
||||||
|
isSpeaking.postValue(device.isSpeaking)
|
||||||
|
Log.i(
|
||||||
|
"$TAG Participant [${device.address.asStringUriOnly()}] is in state [${device.state}]"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun destroy() {
|
||||||
|
device.removeListener(deviceListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
124
app/src/main/java/org/linphone/ui/call/view/GridBoxLayout.kt
Normal file
124
app/src/main/java/org/linphone/ui/call/view/GridBoxLayout.kt
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2022 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.view
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.GridLayout
|
||||||
|
import androidx.annotation.UiThread
|
||||||
|
import androidx.core.view.children
|
||||||
|
import org.linphone.core.tools.Log
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
class GridBoxLayout : GridLayout {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "[Grid Box Layout]"
|
||||||
|
|
||||||
|
private val placementMatrix = arrayOf(
|
||||||
|
intArrayOf(1, 2, 3, 4, 5, 6),
|
||||||
|
intArrayOf(1, 1, 2, 2, 3, 3),
|
||||||
|
intArrayOf(1, 1, 1, 2, 2, 2),
|
||||||
|
intArrayOf(1, 1, 1, 1, 2, 2),
|
||||||
|
intArrayOf(1, 1, 1, 1, 1, 2),
|
||||||
|
intArrayOf(1, 1, 1, 1, 1, 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context) : this(context, null)
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||||
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||||
|
context,
|
||||||
|
attrs,
|
||||||
|
defStyleAttr
|
||||||
|
)
|
||||||
|
|
||||||
|
private var centerContent: Boolean = true
|
||||||
|
private var previousChildCount = 0
|
||||||
|
private var previousCellSize = 0
|
||||||
|
|
||||||
|
@SuppressLint("DrawAllocation")
|
||||||
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
if (childCount == 0 || (!changed && previousChildCount == childCount)) {
|
||||||
|
super.onLayout(changed, left, top, right, bottom)
|
||||||
|
// To prevent display issue the first time conference is locally paused
|
||||||
|
children.forEach { child ->
|
||||||
|
child.post {
|
||||||
|
child.layoutParams.width = previousCellSize
|
||||||
|
child.layoutParams.height = previousCellSize
|
||||||
|
child.requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// To prevent java.lang.IllegalArgumentException: columnCount must be greater than or equal
|
||||||
|
// to the maximum of all grid indices (and spans) defined in the LayoutParams of each child.
|
||||||
|
children.forEach { child ->
|
||||||
|
child.layoutParams = LayoutParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxChild = placementMatrix[0].size
|
||||||
|
if (childCount > maxChild) {
|
||||||
|
val maxMosaicParticipants = 6
|
||||||
|
Log.e(
|
||||||
|
"$TAG $childCount children but placementMatrix only knows how to display $maxChild (max allowed participants for grid layout in settings is $maxMosaicParticipants)"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val availableSize = Pair(right - left, bottom - top)
|
||||||
|
var cellSize = 0
|
||||||
|
for (index in 1..childCount) {
|
||||||
|
val neededColumns = placementMatrix[index - 1][childCount - 1]
|
||||||
|
val candidateWidth = 1 * availableSize.first / neededColumns
|
||||||
|
val candidateHeight = 1 * availableSize.second / index
|
||||||
|
val candidateSize = if (candidateWidth < candidateHeight) candidateWidth else candidateHeight
|
||||||
|
if (candidateSize > cellSize) {
|
||||||
|
columnCount = neededColumns
|
||||||
|
rowCount = index
|
||||||
|
cellSize = candidateSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previousCellSize = cellSize
|
||||||
|
previousChildCount = childCount
|
||||||
|
|
||||||
|
super.onLayout(changed, left, top, right, bottom)
|
||||||
|
children.forEach { child ->
|
||||||
|
child.layoutParams.width = cellSize
|
||||||
|
child.layoutParams.height = cellSize
|
||||||
|
child.post {
|
||||||
|
child.requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centerContent) {
|
||||||
|
setPadding(
|
||||||
|
(availableSize.first - (columnCount * cellSize)) / 2,
|
||||||
|
(availableSize.second - (rowCount * cellSize)) / 2,
|
||||||
|
(availableSize.first - (columnCount * cellSize)) / 2,
|
||||||
|
(availableSize.second - (rowCount * cellSize)) / 2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Log.d(
|
||||||
|
"$TAG cellsize=$cellSize columns=$columnCount rows=$rowCount availablesize=$availableSize"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -411,6 +411,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
coreContext.postOnCoreThread { core ->
|
coreContext.postOnCoreThread { core ->
|
||||||
core.removeListener(coreListener)
|
core.removeListener(coreListener)
|
||||||
|
conferenceModel.destroy()
|
||||||
contact.value?.destroy()
|
contact.value?.destroy()
|
||||||
|
|
||||||
if (::currentCall.isInitialized) {
|
if (::currentCall.isInitialized) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<corners android:radius="20dp" />
|
||||||
|
<stroke android:color="@color/gray_main2_200" android:width="3dp"/>
|
||||||
|
</shape>
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:layout_marginBottom="@dimen/call_main_actions_menu_height"
|
android:layout_marginBottom="@dimen/call_main_actions_menu_height"
|
||||||
|
android:visibility="@{conferenceViewModel.participantDevices.size() > 1 ? View.GONE : View.VISIBLE}"
|
||||||
app:layout_constraintTop_toBottomOf="@id/call_direction_label"
|
app:layout_constraintTop_toBottomOf="@id/call_direction_label"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|
@ -88,7 +89,7 @@
|
||||||
android:paddingBottom="12dp"
|
android:paddingBottom="12dp"
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="20dp"
|
||||||
android:paddingEnd="20dp"
|
android:paddingEnd="20dp"
|
||||||
android:background="@drawable/shape_squircle_main2_400_background"
|
android:background="@drawable/shape_squircle_main2_400_border"
|
||||||
android:text="@string/conference_share_link_title"
|
android:text="@string/conference_share_link_title"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:textColor="@color/gray_main2_400"
|
android:textColor="@color/gray_main2_400"
|
||||||
|
|
@ -99,6 +100,22 @@
|
||||||
app:layout_constraintStart_toStartOf="@id/background"
|
app:layout_constraintStart_toStartOf="@id/background"
|
||||||
app:layout_constraintEnd_toEndOf="@id/background"/>
|
app:layout_constraintEnd_toEndOf="@id/background"/>
|
||||||
|
|
||||||
|
<org.linphone.ui.call.view.GridBoxLayout
|
||||||
|
android:id="@+id/grid_box_layout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_marginBottom="@dimen/call_main_actions_menu_height"
|
||||||
|
android:onClick="@{() -> viewModel.toggleFullScreen()}"
|
||||||
|
android:visibility="@{conferenceViewModel.participantDevices.size() > 1 ? View.VISIBLE : View.GONE}"
|
||||||
|
entries="@{conferenceViewModel.participantDevices}"
|
||||||
|
layout="@{@layout/call_conference_grid_cell}"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/call_direction_label"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Group
|
<androidx.constraintlayout.widget.Group
|
||||||
android:id="@+id/header_info_visibility"
|
android:id="@+id/header_info_visibility"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
@ -202,12 +219,6 @@
|
||||||
bind:callsViewModel="@{callsViewModel}"
|
bind:callsViewModel="@{callsViewModel}"
|
||||||
bind:callsListClickListener="@{callsListClickListener}"/>
|
bind:callsListClickListener="@{callsListClickListener}"/>
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/call_numpad"
|
|
||||||
android:visibility="@{viewModel.fullScreenMode || viewModel.pipMode ? View.INVISIBLE : View.VISIBLE}"
|
|
||||||
layout="@layout/call_numpad"
|
|
||||||
bind:model="@{numpadModel}"/>
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
</layout>
|
</layout>
|
||||||
91
app/src/main/res/layout/call_conference_grid_cell.xml
Normal file
91
app/src/main/res/layout/call_conference_grid_cell.xml
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?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="model"
|
||||||
|
type="org.linphone.ui.call.model.ConferenceParticipantDeviceModel" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:background="@drawable/shape_round_in_call_gray_background"
|
||||||
|
app:layout_constraintDimensionRatio="1:1">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
style="@style/avatar_imageview"
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
coilCallAvatar="@{model.avatarModel}"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintWidth_max="@dimen/avatar_in_call_size"
|
||||||
|
app:layout_constraintHeight_max="@dimen/avatar_in_call_size"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/trust_badge"
|
||||||
|
android:layout_width="@dimen/avatar_presence_badge_in_call_size"
|
||||||
|
android:layout_height="@dimen/avatar_presence_badge_in_call_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"/>
|
||||||
|
|
||||||
|
<org.linphone.ui.call.view.RoundCornersTextureView
|
||||||
|
android:id="@+id/participant_video_surface"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
app:alignTopRight="false"
|
||||||
|
app:displayMode="hybrid"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/muted"
|
||||||
|
android:layout_width="@dimen/icon_size"
|
||||||
|
android:layout_height="@dimen/icon_size"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:src="@drawable/microphone_slash"
|
||||||
|
android:background="@drawable/circle_white_button_background"
|
||||||
|
android:visibility="@{model.isMuted ? View.VISIBLE : View.GONE}"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="@style/default_text_style_500"
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:text="@{model.avatarModel.name, default=`John Doe`}"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/speaking"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:src="@drawable/shape_squircle_main2_200_border"
|
||||||
|
android:visibility="@{model.isSpeaking ? View.VISIBLE : View.GONE}" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</layout>
|
||||||
Loading…
Add table
Reference in a new issue