Changes to reflect new conference API

Should fix various conference UI issues

More UI fixes related to conference

Fixes & improvements

Fixed remove participant button invisible in dark mode

API changes

Handle conference enter and leave

Fixed conference UI issues

Small UI improvements

Improve logging

Improved paused conference cell

Use isMe if participant added or removed is focus

Update method names after changes to conference API
This commit is contained in:
Sylvain Berfini 2020-07-16 11:15:19 +02:00
parent 63205cb33f
commit b803ae9a61
19 changed files with 506 additions and 371 deletions

View file

@ -203,11 +203,17 @@ repositories {
maven {
name "local linphone-sdk maven repository"
url file(LinphoneSdkBuildDir + '/maven_repository/')
content {
includeGroup "org.linphone"
}
}
maven {
name "linphone.org maven repository"
url "https://linphone.org/maven_repository"
content {
includeGroup "org.linphone"
}
}
}

View file

@ -39,7 +39,7 @@ abstract class ProximitySensorActivity : GenericActivity() {
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { }
override fun onSensorChanged(event: SensorEvent) {
if (event.timestamp == 0L) return
if (event.timestamp == 0L || !proximitySensorEnabled) return
if (isProximitySensorNearby(event)) {
if (!proximityWakeLock.isHeld) {
Log.i("[Proximity Sensor Activity] Acquiring proximity wake lock")
@ -89,6 +89,12 @@ abstract class ProximitySensorActivity : GenericActivity() {
super.onPause()
}
override fun onDestroy() {
enableProximitySensor(false)
super.onDestroy()
}
protected fun enableProximitySensor(enable: Boolean) {
if (!proximitySensorFound) {
Log.w("[Proximity Sensor Activity] Couldn't find proximity sensor in this device, skipping")

View file

@ -35,6 +35,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.activities.GenericFragment
import org.linphone.activities.call.viewmodels.CallsViewModel
import org.linphone.activities.call.viewmodels.ConferenceViewModel
import org.linphone.activities.call.viewmodels.ControlsViewModel
import org.linphone.activities.call.viewmodels.SharedCallViewModel
import org.linphone.activities.main.MainActivity
@ -51,6 +52,7 @@ import org.linphone.utils.PermissionHelper
class ControlsFragment : GenericFragment<CallControlsFragmentBinding>() {
private lateinit var callsViewModel: CallsViewModel
private lateinit var controlsViewModel: ControlsViewModel
private lateinit var conferenceViewModel: ConferenceViewModel
private lateinit var sharedViewModel: SharedCallViewModel
private var dialog: Dialog? = null
@ -75,6 +77,9 @@ class ControlsFragment : GenericFragment<CallControlsFragmentBinding>() {
controlsViewModel = ViewModelProvider(this).get(ControlsViewModel::class.java)
binding.controlsViewModel = controlsViewModel
conferenceViewModel = ViewModelProvider(this).get(ConferenceViewModel::class.java)
binding.conferenceViewModel = conferenceViewModel
callsViewModel.currentCallViewModel.observe(viewLifecycleOwner, {
if (it != null) {
binding.activeCallTimer.base =

View file

@ -22,9 +22,8 @@ package org.linphone.activities.call.viewmodels
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Call
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.*
import org.linphone.core.tools.Log
import org.linphone.utils.Event
import org.linphone.utils.PermissionHelper
@ -35,10 +34,6 @@ class CallsViewModel : ViewModel() {
val pausedCalls = MutableLiveData<ArrayList<CallViewModel>>()
val conferenceCalls = MutableLiveData<ArrayList<CallViewModel>>()
val isConferencePaused = MutableLiveData<Boolean>()
val noMoreCallEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
@ -52,14 +47,9 @@ class CallsViewModel : ViewModel() {
}
private val listener = object : CoreListenerStub() {
override fun onCallStateChanged(
core: Core,
call: Call,
state: Call.State,
message: String
) {
callPausedByRemote.value = state == Call.State.PausedByRemote
isConferencePaused.value = !coreContext.core.isInConference
override fun onCallStateChanged(core: Core, call: Call, state: Call.State, message: String) {
Log.i("[Calls VM] Call state changed: $state")
callPausedByRemote.value = (state == Call.State.PausedByRemote) and (call.conference == null)
val currentCall = core.currentCall
if (currentCall == null) {
@ -71,14 +61,11 @@ class CallsViewModel : ViewModel() {
if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) {
if (core.callsNb == 0) {
noMoreCallEvent.value = Event(true)
conferenceCalls.value = arrayListOf()
} else {
removeCallFromPausedListIfPresent(call)
removeCallFromConferenceIfPresent(call)
}
} else if (state == Call.State.Paused) {
addCallToPausedList(call)
removeCallFromConferenceIfPresent(call)
} else if (state == Call.State.Resuming) {
removeCallFromPausedListIfPresent(call)
} else if (call.state == Call.State.UpdatedByRemote) {
@ -91,16 +78,8 @@ class CallsViewModel : ViewModel() {
call.deferUpdate()
callUpdateEvent.value = Event(call)
}
} else {
if (state == Call.State.StreamsRunning) {
callUpdateEvent.value = Event(call)
}
if (call.conference != null) {
addCallToConferenceListIfNotAlreadyInIt(call)
} else {
removeCallFromConferenceIfPresent(call)
}
} else if (state == Call.State.StreamsRunning) {
callUpdateEvent.value = Event(call)
}
}
}
@ -112,20 +91,14 @@ class CallsViewModel : ViewModel() {
if (currentCall != null) {
currentCallViewModel.value = CallViewModel(currentCall)
}
callPausedByRemote.value = currentCall?.state == Call.State.PausedByRemote
isConferencePaused.value = !coreContext.core.isInConference
val conferenceList = arrayListOf<CallViewModel>()
callPausedByRemote.value = currentCall?.state == Call.State.PausedByRemote
for (call in coreContext.core.calls) {
if (call.state == Call.State.Paused || call.state == Call.State.Pausing) {
addCallToPausedList(call)
} else {
if (call.conference != null) {
conferenceList.add(CallViewModel(call))
}
}
}
conferenceCalls.value = conferenceList
}
override fun onCleared() {
@ -138,20 +111,6 @@ class CallsViewModel : ViewModel() {
coreContext.answerCallVideoUpdateRequest(call, accept)
}
fun pauseConference() {
if (coreContext.core.isInConference) {
coreContext.core.leaveConference()
isConferencePaused.value = true
}
}
fun resumeConference() {
if (!coreContext.core.isInConference) {
coreContext.core.enterConference()
isConferencePaused.value = false
}
}
fun takeScreenshot() {
if (!PermissionHelper.get().hasWriteExternalStorage()) {
askWriteExternalStoragePermissionEvent.value = Event(true)
@ -161,9 +120,17 @@ class CallsViewModel : ViewModel() {
}
private fun addCallToPausedList(call: Call) {
if (call.conference != null) return // Conference will be displayed as paused, no need to display the call as well
val list = arrayListOf<CallViewModel>()
list.addAll(pausedCalls.value.orEmpty())
for (pausedCallViewModel in list) {
if (pausedCallViewModel.call == call) {
return
}
}
val viewModel = CallViewModel(call)
list.add(viewModel)
pausedCalls.value = list
@ -182,31 +149,4 @@ class CallsViewModel : ViewModel() {
pausedCalls.value = list
}
private fun addCallToConferenceListIfNotAlreadyInIt(call: Call) {
val list = arrayListOf<CallViewModel>()
list.addAll(conferenceCalls.value.orEmpty())
for (viewModel in list) {
if (viewModel.call == call) return
}
val viewModel = CallViewModel(call)
list.add(viewModel)
conferenceCalls.value = list
}
private fun removeCallFromConferenceIfPresent(call: Call) {
val list = arrayListOf<CallViewModel>()
list.addAll(conferenceCalls.value.orEmpty())
for (viewModel in list) {
if (viewModel.call == call) {
list.remove(viewModel)
break
}
}
conferenceCalls.value = list
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2010-2020 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.activities.call.viewmodels
import androidx.lifecycle.MutableLiveData
import org.linphone.contact.GenericContactViewModel
import org.linphone.core.Conference
import org.linphone.core.Participant
import org.linphone.core.tools.Log
class ConferenceParticipantViewModel(
private val conference: Conference,
val participant: Participant
) :
GenericContactViewModel(participant.address) {
private val isAdmin = MutableLiveData<Boolean>()
val isMeAdmin = MutableLiveData<Boolean>()
init {
isAdmin.value = participant.isAdmin
isMeAdmin.value = conference.me.isAdmin
Log.i("[Conference Participant VM] Participant ${participant.address.asStringUriOnly()} is ${if (participant.isAdmin) "admin" else "not admin"}")
Log.i("[Conference Participant VM] Me is ${if (conference.me.isAdmin) "admin" else "not admin"} and is ${if (conference.me.isFocus) "focus" else "not focus"}")
}
fun removeFromConference() {
Log.i("[Conference Participant VM] Removing participant ${participant.address.asStringUriOnly()} from conference $conference")
conference.removeParticipant(participant)
}
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 2010-2020 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.activities.call.viewmodels
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.*
import org.linphone.core.tools.Log
class ConferenceViewModel : ViewModel() {
val isConferencePaused = MutableLiveData<Boolean>()
val isMeConferenceFocus = MutableLiveData<Boolean>()
val conferenceAddress = MutableLiveData<Address>()
val conferenceParticipants = MutableLiveData<List<ConferenceParticipantViewModel>>()
val isInConference = MutableLiveData<Boolean>()
private val conferenceListener = object : ConferenceListenerStub() {
override fun onParticipantAdded(conference: Conference, participant: Participant) {
if (conference.isMe(participant.address)) {
Log.i("[Conference VM] Entered conference")
isConferencePaused.value = false
} else {
Log.i("[Conference VM] Participant added")
updateParticipantsList(conference)
}
}
override fun onParticipantRemoved(conference: Conference, participant: Participant) {
if (conference.isMe(participant.address)) {
Log.i("[Conference VM] Left conference")
isConferencePaused.value = true
} else {
Log.i("[Conference VM] Participant removed")
updateParticipantsList(conference)
}
}
override fun onParticipantAdminStatusChanged(
conference: Conference,
participant: Participant
) {
Log.i("[Conference VM] Participant admin status changed")
updateParticipantsList(conference)
}
}
private val listener = object : CoreListenerStub() {
override fun onConferenceStateChanged(
core: Core,
conference: Conference,
state: Conference.State
) {
Log.i("[Conference VM] Conference state changed: $state")
isConferencePaused.value = !conference.isIn
if (state == Conference.State.Instantiated) {
conference.addListener(conferenceListener)
} else if (state == Conference.State.Created) {
updateParticipantsList(conference)
isMeConferenceFocus.value = conference.me.isFocus
conferenceAddress.value = conference.conferenceAddress
} else if (state == Conference.State.Terminated || state == Conference.State.TerminationFailed) {
isInConference.value = false
conference.removeListener(conferenceListener)
conferenceParticipants.value = arrayListOf()
}
}
}
init {
coreContext.core.addListener(listener)
isConferencePaused.value = coreContext.core.conference?.isIn != true
isMeConferenceFocus.value = false
conferenceParticipants.value = arrayListOf()
isInConference.value = false
val conference = coreContext.core.conference
if (conference != null) {
conference.addListener(conferenceListener)
isMeConferenceFocus.value = conference.me.isFocus
updateParticipantsList(conference)
}
}
override fun onCleared() {
coreContext.core.removeListener(listener)
super.onCleared()
}
fun pauseConference() {
val defaultProxyConfig = coreContext.core.defaultProxyConfig
val localAddress = defaultProxyConfig?.identityAddress
val participants = arrayOf<Address>()
val remoteConference = coreContext.core.searchConference(null, localAddress, conferenceAddress.value, participants)
val localConference = coreContext.core.searchConference(null, conferenceAddress.value, conferenceAddress.value, participants)
val conference = remoteConference ?: localConference
if (conference != null) {
Log.i("[Conference VM] Leaving conference with address ${conferenceAddress.value?.asStringUriOnly()} temporarily")
conference.leave()
} else {
Log.w("[Conference VM] Unable to find conference with address ${conferenceAddress.value?.asStringUriOnly()}")
}
}
fun resumeConference() {
val defaultProxyConfig = coreContext.core.defaultProxyConfig
val localAddress = defaultProxyConfig?.identityAddress
val participants = arrayOf<Address>()
val remoteConference = coreContext.core.searchConference(null, localAddress, conferenceAddress.value, participants)
val localConference = coreContext.core.searchConference(null, conferenceAddress.value, conferenceAddress.value, participants)
val conference = remoteConference ?: localConference
if (conference != null) {
Log.i("[Conference VM] Entering again conference with address ${conferenceAddress.value?.asStringUriOnly()}")
conference.enter()
} else {
Log.w("[Conference VM] Unable to find conference with address ${conferenceAddress.value?.asStringUriOnly()}")
}
}
private fun updateParticipantsList(conference: Conference) {
val participants = arrayListOf<ConferenceParticipantViewModel>()
for (participant in conference.participantList) {
Log.i("[Conference VM] Participant found: ${participant.address.asStringUriOnly()}")
val viewModel = ConferenceParticipantViewModel(conference, participant)
participants.add(viewModel)
}
conferenceParticipants.value = participants
isInConference.value = participants.isNotEmpty()
}
}

View file

@ -285,8 +285,8 @@ class ControlsViewModel : ViewModel() {
if (conference != null && core.isInConference) {
val params = core.createConferenceParams()
val videoEnabled = conference.currentParams.videoEnabled()
params.enableVideo(!videoEnabled)
val videoEnabled = conference.currentParams.isVideoEnabled
params.isVideoEnabled = !videoEnabled
Log.i("[Controls VM] Conference current param for video is $videoEnabled")
conference.updateParams(params)
} else if (currentCall != null) {
@ -362,7 +362,7 @@ class ControlsViewModel : ViewModel() {
val currentCallVideoEnabled = core.currentCall?.currentParams?.videoEnabled() ?: false
val params = core.createConferenceParams()
params.enableVideo(currentCallVideoEnabled)
params.isVideoEnabled = currentCallVideoEnabled
Log.i("[Call] Setting videoEnabled to [$currentCallVideoEnabled] in conference params")
val conference = core.conference ?: core.createConferenceWithParams(params)

View file

@ -37,16 +37,8 @@ class StatisticsListViewModel : ViewModel() {
state: Call.State,
message: String
) {
if (state == Call.State.End || state == Call.State.Error) {
val newList = arrayListOf<CallStatisticsData>()
for (stat in callStatsList.value.orEmpty()) {
if (stat.call != call) {
newList.add(stat)
} else {
stat.destroy()
}
}
callStatsList.value = newList
if (state == Call.State.End || state == Call.State.Error || state == Call.State.Connected) {
computeCallsList()
}
}
}
@ -54,13 +46,7 @@ class StatisticsListViewModel : ViewModel() {
init {
coreContext.core.addListener(listener)
val list = arrayListOf<CallStatisticsData>()
for (call in coreContext.core.calls) {
if (call.state != Call.State.End && call.state != Call.State.Released && call.state != Call.State.Error) {
list.add(CallStatisticsData(call))
}
}
callStatsList.value = list
computeCallsList()
}
override fun onCleared() {
@ -69,4 +55,14 @@ class StatisticsListViewModel : ViewModel() {
super.onCleared()
}
private fun computeCallsList() {
val list = arrayListOf<CallStatisticsData>()
for (call in coreContext.core.calls) {
if (call.state != Call.State.End && call.state != Call.State.Released && call.state != Call.State.Error) {
list.add(CallStatisticsData(call))
}
}
callStatsList.value = list
}
}

View file

@ -133,7 +133,7 @@ class StatusViewModel : StatusViewModel() {
}
private fun updateCallQualityIcon() {
val call = coreContext.core.currentCall
val call = coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull()
val quality = call?.currentQuality ?: 0f
callQualityIcon.value = when {
quality >= 4 -> R.drawable.call_quality_indicator_4

View file

@ -26,11 +26,12 @@ import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.databinding.DataBindingUtil
import org.linphone.R
import org.linphone.activities.call.viewmodels.CallViewModel
import org.linphone.databinding.CallConferenceBinding
import org.linphone.activities.call.viewmodels.ConferenceParticipantViewModel
import org.linphone.core.tools.Log
import org.linphone.databinding.CallConferenceParticipantBinding
class ConferenceCallView : LinearLayout {
private lateinit var binding: CallConferenceBinding
class ConferenceParticipantView : LinearLayout {
private lateinit var binding: CallConferenceParticipantBinding
constructor(context: Context) : super(context) {
init(context)
@ -53,15 +54,18 @@ class ConferenceCallView : LinearLayout {
fun init(context: Context) {
binding = DataBindingUtil.inflate(
LayoutInflater.from(context), R.layout.call_conference, this, true
LayoutInflater.from(context), R.layout.call_conference_participant, this, true
)
}
fun setViewModel(viewModel: CallViewModel) {
fun setViewModel(viewModel: ConferenceParticipantViewModel) {
binding.viewModel = viewModel
binding.callTimer.base =
SystemClock.elapsedRealtime() - (1000 * viewModel.call.duration) // Linphone timestamps are in seconds
val currentTimeSecs = System.currentTimeMillis()
val participantTime = viewModel.participant.creationTime * 1000 // Linphone timestamps are in seconds
val diff = currentTimeSecs - participantTime
Log.i("[Conference Participant] Participant joined conference at $participantTime == ${diff / 1000} seconds ago.")
binding.callTimer.base = SystemClock.elapsedRealtime() - diff
binding.callTimer.start()
}
}

View file

@ -506,7 +506,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
fun isVideoCallOrConferenceActive(): Boolean {
val conference = core.conference
return if (conference != null && core.isInConference) {
conference.currentParams.videoEnabled()
conference.currentParams.isVideoEnabled()
} else {
core.currentCall?.currentParams?.videoEnabled() ?: false
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:src="@drawable/options_start_conference_default"
android:tint="?attr/drawableTintColor2"/>
</item>
</selector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:src="@drawable/conference_exit_default"
android:tint="?attr/drawableTintColor"/>
</item>
</selector>

View file

@ -12,6 +12,9 @@
<variable
name="controlsViewModel"
type="org.linphone.activities.call.viewmodels.ControlsViewModel" />
<variable
name="conferenceViewModel"
type="org.linphone.activities.call.viewmodels.ConferenceViewModel" />
</data>
<RelativeLayout
@ -26,7 +29,7 @@
<LinearLayout
android:id="@+id/active_call_header"
android:visibility="@{viewModel.currentCallViewModel == null ? View.GONE : View.VISIBLE}"
android:visibility="@{viewModel.currentCallViewModel == null || conferenceViewModel.isInConference() ? View.GONE : View.VISIBLE}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
@ -54,75 +57,16 @@
</LinearLayout>
<LinearLayout
android:visibility="@{viewModel.conferenceCalls.size() == 0 || viewModel.isConferencePaused ? View.GONE : View.VISIBLE, default=gone}"
<include layout="@layout/call_conference"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="?attr/backgroundColor"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingLeft="10dp"
android:textColor="?attr/primaryTextColor"
android:textSize="30sp"
android:text="@string/call_conference_title" />
<ImageView
android:onClick="@{() -> controlsViewModel.switchCamera()}"
android:visibility="@{controlsViewModel.isVideoEnabled ? View.VISIBLE : View.GONE, default=gone}"
android:contentDescription="@string/content_description_switch_camera"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/pause_conference"
android:adjustViewBounds="true"
android:padding="10dp"
android:scaleType="fitCenter"
android:background="@drawable/round_button_background"
android:src="@drawable/camera_switch" />
<ImageView
android:id="@+id/pause_conference"
android:onClick="@{() -> viewModel.pauseConference()}"
android:contentDescription="@string/content_description_pause_conference"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginRight="10dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:padding="10dp"
android:scaleType="fitCenter"
android:background="@drawable/round_button_background"
android:src="@drawable/call_pause" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dividerColor" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:orientation="vertical"
app:entries="@{viewModel.conferenceCalls}"
app:layout="@{@layout/call_conference_cell}"/>
</LinearLayout>
app:controlsViewModel="@{controlsViewModel}"
app:conferenceViewModel="@{conferenceViewModel}" />
<org.linphone.contact.BigContactAvatarView
android:visibility="@{controlsViewModel.isVideoEnabled ? View.GONE : View.VISIBLE, default=gone}"
android:visibility="@{controlsViewModel.isVideoEnabled || conferenceViewModel.isInConference ? View.GONE : View.VISIBLE, default=gone}"
android:id="@+id/avatar"
android:layout_width="200dp"
android:layout_height="200dp"
@ -169,51 +113,15 @@
app:entries="@{viewModel.pausedCalls}"
app:layout="@{@layout/call_paused_cell}" />
<LinearLayout
android:visibility="@{viewModel.isConferencePaused &amp;&amp; viewModel.conferenceCalls.size() > 1 ? View.VISIBLE : View.GONE, default=gone}"
<include layout="@layout/conference_paused"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_above="@id/paused_calls"
android:alpha="0.5"
android:background="?attr/accentColor"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/content_description_start_conference"
android:src="@drawable/options_start_conference" />
<TextView
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical|left"
android:singleLine="true"
android:ellipsize="end"
android:paddingLeft="20dp"
android:paddingRight="10dp"
android:textColor="?attr/secondaryTextColor"
android:textSize="15sp"
android:text="@string/call_conference_title" />
<ImageView
android:onClick="@{() -> viewModel.resumeConference()}"
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center_vertical"
android:contentDescription="@string/content_description_resume_conference"
android:background="@drawable/round_button_background"
android:padding="10dp"
android:src="@drawable/call_pause" />
</LinearLayout>
app:conferenceViewModel="@{conferenceViewModel}" />
<ImageView
android:onClick="@{() -> viewModel.currentCallViewModel.pause()}"
android:visibility="@{viewModel.currentCallViewModel == null ? View.GONE : View.VISIBLE}"
android:visibility="@{viewModel.currentCallViewModel == null || conferenceViewModel.isInConference ? View.GONE : View.VISIBLE}"
android:selected="@{viewModel.currentCallViewModel.isPaused ?? false}"
android:enabled="@{!viewModel.callPausedByRemote &amp;&amp; controlsViewModel.isPauseEnabled}"
android:contentDescription="@string/content_description_pause_call"
@ -230,7 +138,7 @@
android:id="@+id/switch_camera"
android:onClick="@{() -> controlsViewModel.switchCamera()}"
android:enabled="@{!viewModel.callPausedByRemote}"
android:visibility="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.showSwitchCamera &amp;&amp; (viewModel.conferenceCalls.size() == 0 || viewModel.isConferencePaused) ? View.VISIBLE : View.GONE, default=gone}"
android:visibility="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.showSwitchCamera &amp;&amp; !conferenceViewModel.isInConference ? View.VISIBLE : View.GONE, default=gone}"
android:contentDescription="@string/content_description_switch_camera"
android:layout_width="50dp"
android:layout_height="50dp"
@ -244,7 +152,7 @@
<ImageView
android:onClick="@{() -> viewModel.takeScreenshot()}"
android:enabled="@{!viewModel.callPausedByRemote}"
android:visibility="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.takeScreenshotEnabled &amp;&amp; (viewModel.conferenceCalls.size() == 0 || viewModel.isConferencePaused) ? View.VISIBLE : View.GONE, default=gone}"
android:visibility="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.takeScreenshotEnabled &amp;&amp; !conferenceViewModel.isInConference ? View.VISIBLE : View.GONE, default=gone}"
android:contentDescription="@string/content_description_take_screenshot"
android:layout_width="50dp"
android:layout_height="50dp"

View file

@ -1,79 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<layout xmlns:tools="http://schemas.android.com/tools"
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="viewModel"
type="org.linphone.activities.call.viewmodels.CallViewModel" />
name="controlsViewModel"
type="org.linphone.activities.call.viewmodels.ControlsViewModel" />
<variable
name="conferenceViewModel"
type="org.linphone.activities.call.viewmodels.ConferenceViewModel" />
</data>
<LinearLayout
android:visibility="@{!conferenceViewModel.isInConference || conferenceViewModel.isConferencePaused ? View.GONE : View.VISIBLE}"
android:layout_width="match_parent"
android:layout_height="70dp"
android:layout_height="wrap_content"
android:background="?attr/backgroundColor"
android:orientation="vertical">
<LinearLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_margin="10dp">
android:layout_height="60dp">
<org.linphone.contact.ContactAvatarView
android:id="@+id/avatar"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:viewModel="@{viewModel}"
tools:layout="@layout/contact_avatar" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:paddingRight="10dp"
android:orientation="vertical">
<org.linphone.views.MarqueeTextView
android:text="@{viewModel.contact.fullName ?? viewModel.displayName}"
style="@style/contact_name_list_cell_font"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|left"
android:singleLine="true"/>
<Chronometer
android:id="@+id/call_timer"
android:textColor="?attr/accentColor"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical|right" />
</LinearLayout>
android:layout_centerVertical="true"
android:paddingLeft="10dp"
android:textColor="?attr/primaryTextColor"
android:textSize="30sp"
android:text="@string/call_conference_title" />
<ImageView
android:onClick="@{() -> viewModel.removeFromConference()}"
android:contentDescription="@string/content_description_remove_from_conference"
android:layout_width="30dp"
android:layout_height="30dp"
android:onClick="@{() -> controlsViewModel.switchCamera()}"
android:visibility="@{controlsViewModel.isVideoEnabled ? View.VISIBLE : View.GONE}"
android:contentDescription="@string/content_description_switch_camera"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginRight="10dp"
android:layout_gravity="center_vertical"
android:layout_toLeftOf="@id/pause_conference"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:gravity="center_vertical"
android:src="@drawable/conference_exit_default" />
android:padding="10dp"
android:scaleType="fitCenter"
android:background="@drawable/round_button_background"
android:src="@drawable/camera_switch" />
</LinearLayout>
<ImageView
android:id="@+id/pause_conference"
android:onClick="@{() -> conferenceViewModel.pauseConference()}"
android:contentDescription="@string/content_description_pause_conference"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginRight="10dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:padding="10dp"
android:scaleType="fitCenter"
android:background="@drawable/round_button_background"
android:src="@drawable/call_pause" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dividerColor" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:entries="@{conferenceViewModel.conferenceParticipants}"
app:layout="@{@layout/call_conference_cell}"/>
</ScrollView>
</LinearLayout>
</layout>

View file

@ -5,10 +5,10 @@
<data>
<variable
name="data"
type="org.linphone.activities.call.viewmodels.CallViewModel" />
type="org.linphone.activities.call.viewmodels.ConferenceParticipantViewModel" />
</data>
<org.linphone.activities.call.views.ConferenceCallView
<org.linphone.activities.call.views.ConferenceParticipantView
android:layout_width="match_parent"
android:layout_height="wrap_content"
bind:viewModel="@{data}"/>

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="org.linphone.activities.call.viewmodels.ConferenceParticipantViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_margin="10dp">
<org.linphone.contact.ContactAvatarView
android:id="@+id/avatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:viewModel="@{viewModel}"
tools:layout="@layout/contact_avatar" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:paddingRight="10dp"
android:orientation="vertical">
<org.linphone.views.MarqueeTextView
android:text="@{viewModel.contact.fullName ?? viewModel.displayName}"
style="@style/contact_name_list_cell_font"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|left"
android:singleLine="true"/>
<Chronometer
android:id="@+id/call_timer"
android:textColor="?attr/accentColor"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical|right" />
</LinearLayout>
<ImageView
android:onClick="@{() -> viewModel.removeFromConference()}"
android:visibility="@{viewModel.isMeAdmin ? View.VISIBLE : View.GONE, default=gone}"
android:contentDescription="@string/content_description_remove_from_conference"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="10dp"
android:layout_gravity="center_vertical"
android:adjustViewBounds="true"
android:gravity="center_vertical"
android:src="@drawable/conference_remove_participant" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dividerColor" />
</LinearLayout>
</layout>

View file

@ -11,6 +11,9 @@
<variable
name="controlsViewModel"
type="org.linphone.activities.call.viewmodels.ControlsViewModel" />
<variable
name="conferenceViewModel"
type="org.linphone.activities.call.viewmodels.ConferenceViewModel" />
</data>
<RelativeLayout
@ -25,7 +28,7 @@
<LinearLayout
android:id="@+id/active_call_header"
android:visibility="@{viewModel.currentCallViewModel == null ? View.GONE : View.VISIBLE}"
android:visibility="@{viewModel.currentCallViewModel == null || conferenceViewModel.isInConference() ? View.GONE : View.VISIBLE}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
@ -53,75 +56,16 @@
</LinearLayout>
<LinearLayout
android:visibility="@{viewModel.conferenceCalls.size() == 0 || viewModel.isConferencePaused ? View.GONE : View.VISIBLE, default=gone}"
<include layout="@layout/call_conference"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="?attr/backgroundColor"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingLeft="10dp"
android:textColor="?attr/primaryTextColor"
android:textSize="30sp"
android:text="@string/call_conference_title" />
<ImageView
android:onClick="@{() -> controlsViewModel.switchCamera()}"
android:visibility="@{controlsViewModel.isVideoEnabled ? View.VISIBLE : View.GONE, default=gone}"
android:contentDescription="@string/content_description_switch_camera"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/pause_conference"
android:adjustViewBounds="true"
android:padding="10dp"
android:scaleType="fitCenter"
android:background="@drawable/round_button_background"
android:src="@drawable/camera_switch" />
<ImageView
android:id="@+id/pause_conference"
android:onClick="@{() -> viewModel.pauseConference()}"
android:contentDescription="@string/content_description_pause_conference"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginRight="10dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:padding="10dp"
android:scaleType="fitCenter"
android:background="@drawable/round_button_background"
android:src="@drawable/call_pause" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dividerColor" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:orientation="vertical"
app:entries="@{viewModel.conferenceCalls}"
app:layout="@{@layout/call_conference_cell}"/>
</LinearLayout>
app:controlsViewModel="@{controlsViewModel}"
app:conferenceViewModel="@{conferenceViewModel}" />
<org.linphone.contact.BigContactAvatarView
android:visibility="@{controlsViewModel.isVideoEnabled ? View.GONE : View.VISIBLE, default=gone}"
android:visibility="@{controlsViewModel.isVideoEnabled || conferenceViewModel.isInConference ? View.GONE : View.VISIBLE, default=gone}"
android:id="@+id/avatar"
android:layout_width="200dp"
android:layout_height="200dp"
@ -148,7 +92,7 @@
android:src="@drawable/waiting_time" />
<TextView
android:textColor="?attr/secondaryTextColor"
android:textColor="@color/white_color"
android:textSize="15sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -169,51 +113,15 @@
app:entries="@{viewModel.pausedCalls}"
app:layout="@{@layout/call_paused_cell}" />
<LinearLayout
android:visibility="@{viewModel.isConferencePaused &amp;&amp; viewModel.conferenceCalls.size() > 1 ? View.VISIBLE : View.GONE, default=gone}"
<include layout="@layout/conference_paused"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_above="@id/paused_calls"
android:alpha="0.5"
android:background="?attr/accentColor"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/content_description_start_conference"
android:src="@drawable/options_start_conference" />
<TextView
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical|left"
android:singleLine="true"
android:ellipsize="end"
android:paddingLeft="20dp"
android:paddingRight="10dp"
android:textColor="?attr/secondaryTextColor"
android:textSize="15sp"
android:text="@string/call_conference_title" />
<ImageView
android:onClick="@{() -> viewModel.resumeConference()}"
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center_vertical"
android:contentDescription="@string/content_description_resume_conference"
android:background="@drawable/round_button_background"
android:padding="10dp"
android:src="@drawable/call_pause" />
</LinearLayout>
app:conferenceViewModel="@{conferenceViewModel}" />
<ImageView
android:onClick="@{() -> viewModel.currentCallViewModel.pause()}"
android:visibility="@{viewModel.currentCallViewModel == null ? View.GONE : View.VISIBLE}"
android:visibility="@{viewModel.currentCallViewModel == null || conferenceViewModel.isInConference ? View.GONE : View.VISIBLE}"
android:selected="@{viewModel.currentCallViewModel.isPaused ?? false}"
android:enabled="@{!viewModel.callPausedByRemote &amp;&amp; controlsViewModel.isPauseEnabled}"
android:contentDescription="@string/content_description_pause_call"
@ -230,7 +138,7 @@
android:id="@+id/switch_camera"
android:onClick="@{() -> controlsViewModel.switchCamera()}"
android:enabled="@{!viewModel.callPausedByRemote}"
android:visibility="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.showSwitchCamera &amp;&amp; (viewModel.conferenceCalls.size() == 0 || viewModel.isConferencePaused) ? View.VISIBLE : View.GONE, default=gone}"
android:visibility="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.showSwitchCamera &amp;&amp; !conferenceViewModel.isInConference ? View.VISIBLE : View.GONE, default=gone}"
android:contentDescription="@string/content_description_switch_camera"
android:layout_width="50dp"
android:layout_height="50dp"
@ -244,7 +152,7 @@
<ImageView
android:onClick="@{() -> viewModel.takeScreenshot()}"
android:enabled="@{!viewModel.callPausedByRemote}"
android:visibility="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.takeScreenshotEnabled &amp;&amp; (viewModel.conferenceCalls.size() == 0 || viewModel.isConferencePaused) ? View.VISIBLE : View.GONE, default=gone}"
android:visibility="@{controlsViewModel.isVideoEnabled &amp;&amp; controlsViewModel.takeScreenshotEnabled &amp;&amp; !conferenceViewModel.isInConference ? View.VISIBLE : View.GONE, default=gone}"
android:contentDescription="@string/content_description_take_screenshot"
android:layout_width="50dp"
android:layout_height="50dp"

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable
name="conferenceViewModel"
type="org.linphone.activities.call.viewmodels.ConferenceViewModel" />
</data>
<LinearLayout
android:visibility="@{conferenceViewModel.isConferencePaused &amp;&amp; conferenceViewModel.isInConference ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="50dp"
android:alpha="0.5"
android:background="?attr/accentColor"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="5dp"
android:contentDescription="@string/content_description_start_conference"
android:src="@drawable/conference" />
<TextView
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical|left"
android:singleLine="true"
android:ellipsize="end"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textColor="?attr/secondaryTextColor"
android:textSize="21sp"
android:text="@string/call_conference_title" />
<ImageView
android:onClick="@{() -> conferenceViewModel.resumeConference()}"
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center_vertical"
android:contentDescription="@string/content_description_resume_conference"
android:background="@drawable/round_button_background"
android:padding="10dp"
android:src="@drawable/call_pause" />
</LinearLayout>
</layout>