Split ActiveCallOrConferenceFragment into two fragments

This commit is contained in:
Sylvain Berfini 2022-03-03 15:58:19 +01:00
parent f86c9dc99c
commit 862c5e47c8
12 changed files with 599 additions and 224 deletions

View file

@ -48,10 +48,7 @@ import org.linphone.activities.main.history.fragments.MasterCallLogsFragment
import org.linphone.activities.main.settings.fragments.*
import org.linphone.activities.main.sidemenu.fragments.SideMenuFragment
import org.linphone.activities.voip.CallActivity
import org.linphone.activities.voip.fragments.ActiveCallOrConferenceFragment
import org.linphone.activities.voip.fragments.ConferenceParticipantsFragment
import org.linphone.activities.voip.fragments.IncomingCallFragment
import org.linphone.activities.voip.fragments.OutgoingCallFragment
import org.linphone.activities.voip.fragments.*
import org.linphone.contact.NativeContact
import org.linphone.core.Address
@ -959,11 +956,21 @@ internal fun SideMenuFragment.navigateToScheduledConferences() {
/* Calls related */
internal fun CallActivity.navigateToActiveCall() {
if (findNavController(R.id.nav_host_fragment).currentDestination?.id != R.id.activeCallOrConferenceFragment) {
if (findNavController(R.id.nav_host_fragment).currentDestination?.id != R.id.singleCallFragment) {
findNavController(R.id.nav_host_fragment).navigate(
R.id.action_global_activeCallOrConferenceFragment,
R.id.action_global_singleCallFragment,
null,
popupTo(R.id.activeCallOrConferenceFragment, false)
popupTo(R.id.conferenceCallFragment, true)
)
}
}
internal fun CallActivity.navigateToConferenceCall() {
if (findNavController(R.id.nav_host_fragment).currentDestination?.id != R.id.conferenceCallFragment) {
findNavController(R.id.nav_host_fragment).navigate(
R.id.action_global_conferenceCallFragment,
null,
popupTo(R.id.singleCallFragment, true)
)
}
}
@ -972,7 +979,7 @@ internal fun CallActivity.navigateToOutgoingCall() {
findNavController(R.id.nav_host_fragment).navigate(
R.id.action_global_outgoingCallFragment,
null,
popupTo(R.id.activeCallOrConferenceFragment, false)
popupTo(R.id.singleCallFragment, false)
)
}
@ -982,60 +989,80 @@ internal fun CallActivity.navigateToIncomingCall(earlyMediaVideoEnabled: Boolean
findNavController(R.id.nav_host_fragment).navigate(
R.id.action_global_incomingCallFragment,
args,
popupTo(R.id.activeCallOrConferenceFragment, false)
popupTo(R.id.singleCallFragment, false)
)
}
internal fun OutgoingCallFragment.navigateToActiveCall() {
findNavController().navigate(
R.id.action_global_activeCallOrConferenceFragment,
R.id.action_global_singleCallFragment,
null,
popupTo(R.id.activeCallOrConferenceFragment, false)
popupTo(R.id.singleCallFragment, false)
)
}
internal fun IncomingCallFragment.navigateToActiveCall() {
findNavController().navigate(
R.id.action_global_activeCallOrConferenceFragment,
R.id.action_global_singleCallFragment,
null,
popupTo(R.id.activeCallOrConferenceFragment, false)
popupTo(R.id.singleCallFragment, false)
)
}
internal fun ActiveCallOrConferenceFragment.navigateToCallsList() {
if (findNavController().currentDestination?.id == R.id.activeCallOrConferenceFragment) {
internal fun SingleCallFragment.navigateToCallsList() {
if (findNavController().currentDestination?.id == R.id.singleCallFragment) {
findNavController().navigate(
R.id.action_activeCallOrConferenceFragment_to_callsListFragment,
R.id.action_singleCallFragment_to_callsListFragment,
null,
popupTo()
)
}
}
internal fun ActiveCallOrConferenceFragment.navigateToConferenceParticipants() {
if (findNavController().currentDestination?.id == R.id.activeCallOrConferenceFragment) {
internal fun SingleCallFragment.navigateToConferenceParticipants() {
if (findNavController().currentDestination?.id == R.id.singleCallFragment) {
findNavController().navigate(
R.id.action_activeCallOrConferenceFragment_to_conferenceParticipantsFragment,
R.id.action_singleCallFragment_to_conferenceParticipantsFragment,
null,
popupTo()
)
}
}
internal fun ActiveCallOrConferenceFragment.navigateToChat(args: Bundle) {
if (findNavController().currentDestination?.id == R.id.activeCallOrConferenceFragment) {
internal fun SingleCallFragment.navigateToConferenceLayout() {
if (findNavController().currentDestination?.id == R.id.singleCallFragment) {
findNavController().navigate(
R.id.action_activeCallOrConferenceFragment_to_chatFragment,
args,
R.id.action_singleCallFragment_to_conferenceLayoutFragment,
null,
popupTo()
)
}
}
internal fun ActiveCallOrConferenceFragment.navigateToConferenceLayout() {
if (findNavController().currentDestination?.id == R.id.activeCallOrConferenceFragment) {
internal fun ConferenceCallFragment.navigateToCallsList() {
if (findNavController().currentDestination?.id == R.id.conferenceCallFragment) {
findNavController().navigate(
R.id.action_activeCallOrConferenceFragment_to_conferenceLayoutFragment,
R.id.action_conferenceCallFragment_to_callsListFragment,
null,
popupTo()
)
}
}
internal fun ConferenceCallFragment.navigateToConferenceParticipants() {
if (findNavController().currentDestination?.id == R.id.conferenceCallFragment) {
findNavController().navigate(
R.id.action_conferenceCallFragment_to_conferenceParticipantsFragment,
null,
popupTo()
)
}
}
internal fun ConferenceCallFragment.navigateToConferenceLayout() {
if (findNavController().currentDestination?.id == R.id.conferenceCallFragment) {
findNavController().navigate(
R.id.action_conferenceCallFragment_to_conferenceLayoutFragment,
null,
popupTo()
)

View file

@ -29,27 +29,28 @@ import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import androidx.window.layout.FoldingFeature
import org.linphone.LinphoneApplication
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.activities.ProximitySensorActivity
import org.linphone.activities.*
import org.linphone.activities.main.MainActivity
import org.linphone.activities.navigateToActiveCall
import org.linphone.activities.navigateToIncomingCall
import org.linphone.activities.navigateToOutgoingCall
import org.linphone.activities.voip.viewmodels.CallsViewModel
import org.linphone.activities.voip.viewmodels.ConferenceViewModel
import org.linphone.activities.voip.viewmodels.ControlsViewModel
import org.linphone.compatibility.Compatibility
import org.linphone.core.Call
import org.linphone.core.tools.Log
import org.linphone.databinding.VoipActivityBinding
import org.linphone.mediastream.Version
import org.linphone.utils.Event
import org.linphone.utils.PermissionHelper
class CallActivity : ProximitySensorActivity() {
private lateinit var binding: VoipActivityBinding
private lateinit var controlsViewModel: ControlsViewModel
private lateinit var callsViewModel: CallsViewModel
private lateinit var conferenceViewModel: ConferenceViewModel
private var foldingFeature: FoldingFeature? = null
@ -77,6 +78,8 @@ class CallActivity : ProximitySensorActivity() {
callsViewModel = ViewModelProvider(navControllerStoreOwner)[CallsViewModel::class.java]
conferenceViewModel = ViewModelProvider(navControllerStoreOwner)[ConferenceViewModel::class.java]
callsViewModel.noMoreCallEvent.observe(
this
) {
@ -112,6 +115,39 @@ class CallActivity : ProximitySensorActivity() {
Compatibility.enableAutoEnterPiP(this, enabled)
}
callsViewModel.currentCallData.observe(
this
) { callData ->
if (callData.call.conference == null) {
Log.i("[Call] Current call isn't linked to a conference, changing fragment")
navigateToActiveCall()
} else {
Log.i("[Call] Current call is linked to a conference, changing fragment")
navigateToConferenceCall()
}
}
conferenceViewModel.conferenceExists.observe(
this
) { exists ->
if (exists) {
Log.i("[Call] Found active conference, changing fragment")
navigateToConferenceCall()
} else {
Log.i("[Call] Conference no longer exists, changing fragment")
navigateToActiveCall()
}
}
conferenceViewModel.isConferenceLocallyPaused.observe(
this
) { paused ->
if (!paused) {
Log.i("[Call] Entered conference, make sure conference fragment is active")
navigateToConferenceCall()
}
}
if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) {
checkPermissions()
}
@ -151,28 +187,22 @@ class CallActivity : ProximitySensorActivity() {
} else {
finish()
}
} else {
coreContext.removeCallOverlay()
return
}
coreContext.removeCallOverlay()
val currentCall = coreContext.core.currentCall
if (currentCall == null) {
Log.e("[Call] No current call found, assume active call")
navigateToActiveCall()
return
val currentCall = coreContext.core.currentCall
when (currentCall?.state) {
Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia, Call.State.OutgoingProgress, Call.State.OutgoingRinging -> {
navigateToOutgoingCall()
}
when (currentCall.state) {
Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia, Call.State.OutgoingProgress, Call.State.OutgoingRinging -> {
navigateToOutgoingCall()
}
Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> {
val earlyMediaVideoEnabled = LinphoneApplication.corePreferences.acceptEarlyMedia &&
currentCall.state == Call.State.IncomingEarlyMedia &&
currentCall.currentParams.isVideoEnabled
navigateToIncomingCall(earlyMediaVideoEnabled)
}
else -> navigateToActiveCall()
Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> {
val earlyMediaVideoEnabled = corePreferences.acceptEarlyMedia &&
currentCall.state == Call.State.IncomingEarlyMedia &&
currentCall.currentParams.isVideoEnabled
navigateToIncomingCall(earlyMediaVideoEnabled)
}
else -> {}
}
}
@ -251,21 +281,7 @@ class CallActivity : ProximitySensorActivity() {
}
private fun updateConstraintSetDependingOnFoldingState() {
/*val feature = foldingFeature ?: return
val constraintLayout = binding.constraintLayout
val set = ConstraintSet()
set.clone(constraintLayout)
if (feature.state == FoldingFeature.State.HALF_OPENED && viewModel.videoEnabled.value == true) {
set.setGuidelinePercent(R.id.hinge_top, 0.5f)
set.setGuidelinePercent(R.id.hinge_bottom, 0.5f)
viewModel.disable(true)
} else {
set.setGuidelinePercent(R.id.hinge_top, 0f)
set.setGuidelinePercent(R.id.hinge_bottom, 1f)
viewModel.disable(false)
}
set.applyTo(constraintLayout)*/
val feature = foldingFeature ?: return
controlsViewModel.foldingStateChangedEvent.value = Event(feature.state)
}
}

View file

@ -20,7 +20,6 @@
package org.linphone.activities.voip.data
import android.view.View
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import java.util.*
import kotlinx.coroutines.*
@ -47,7 +46,6 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
val isInRemoteConference = MutableLiveData<Boolean>()
val remoteConferenceSubject = MutableLiveData<String>()
val isActiveAndNotInConference = MediatorLiveData<Boolean>()
val isOutgoing = MutableLiveData<Boolean>()
val isIncoming = MutableLiveData<Boolean>()
@ -92,17 +90,6 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
call.addListener(listener)
isRemotelyRecorded.value = call.remoteParams?.isRecording
isActiveAndNotInConference.value = true
isActiveAndNotInConference.addSource(isPaused) {
updateActiveAndNotInConference()
}
isActiveAndNotInConference.addSource(isRemotelyPaused) {
updateActiveAndNotInConference()
}
isActiveAndNotInConference.addSource(isInRemoteConference) {
updateActiveAndNotInConference()
}
update()
// initChatRoom()
@ -308,7 +295,7 @@ open class CallData(val call: Call) : GenericContactData(call.remoteAddress) {
Log.i("[Call] Starting 30 seconds timer to automatically decline video request")
}
private fun updateActiveAndNotInConference() {
isActiveAndNotInConference.value = isPaused.value == false && isRemotelyPaused.value == false && isInRemoteConference.value == false
fun isActiveAndNotInConference(): Boolean {
return isPaused.value == false && isRemotelyPaused.value == false && isInRemoteConference.value == false
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2021 Belledonne Communications SARL.
* Copyright (c) 2010-2022 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
@ -31,36 +31,33 @@ import android.widget.RelativeLayout
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.navigation.navGraphViewModels
import androidx.window.layout.FoldingFeature
import com.google.android.material.snackbar.Snackbar
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.activities.*
import org.linphone.activities.GenericFragment
import org.linphone.activities.main.MainActivity
import org.linphone.activities.main.viewmodels.DialogViewModel
import org.linphone.activities.navigateToCallsList
import org.linphone.activities.navigateToConferenceLayout
import org.linphone.activities.navigateToConferenceParticipants
import org.linphone.activities.voip.viewmodels.CallsViewModel
import org.linphone.activities.voip.viewmodels.ConferenceViewModel
import org.linphone.activities.voip.viewmodels.ControlsViewModel
import org.linphone.activities.voip.viewmodels.StatisticsListViewModel
import org.linphone.activities.voip.views.RoundCornersTextureView
import org.linphone.core.*
import org.linphone.core.Conference
import org.linphone.core.StreamType
import org.linphone.core.tools.Log
import org.linphone.databinding.VoipActiveCallOrConferenceFragmentBindingImpl
import org.linphone.mediastream.video.capture.CaptureTextureView
import org.linphone.utils.AppUtils
import org.linphone.utils.DialogUtils
import org.linphone.databinding.VoipConferenceCallFragmentBinding
class ActiveCallOrConferenceFragment : GenericFragment<VoipActiveCallOrConferenceFragmentBindingImpl>() {
class ConferenceCallFragment : GenericFragment<VoipConferenceCallFragmentBinding>() {
private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph)
private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph)
private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph)
private val statsViewModel: StatisticsListViewModel by navGraphViewModels(R.id.call_nav_graph)
private var dialog: Dialog? = null
override fun getLayoutId(): Int = R.layout.voip_active_call_or_conference_fragment
override fun getLayoutId(): Int = R.layout.voip_conference_call_fragment
override fun onStart() {
useMaterialSharedAxisXForwardAnimation = false
@ -146,17 +143,6 @@ class ActiveCallOrConferenceFragment : GenericFragment<VoipActiveCallOrConferenc
}
}
callsViewModel.currentCallData.observe(
viewLifecycleOwner
) {
if (it != null) {
val timer = binding.root.findViewById<Chronometer>(R.id.active_call_timer)
timer.base =
SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds
timer.start()
}
}
controlsViewModel.goToConferenceParticipantsListEvent.observe(
viewLifecycleOwner
) {
@ -181,7 +167,7 @@ class ActiveCallOrConferenceFragment : GenericFragment<VoipActiveCallOrConferenc
}
}
controlsViewModel.goToConferenceLayoutSettings.observe(
controlsViewModel.goToConferenceLayoutSettingsEvent.observe(
viewLifecycleOwner
) {
it.consume {
@ -189,24 +175,18 @@ class ActiveCallOrConferenceFragment : GenericFragment<VoipActiveCallOrConferenc
}
}
controlsViewModel.foldingStateChangedEvent.observe(
viewLifecycleOwner
) {
it.consume { state ->
updateHingeRelatedConstraints(state)
}
}
callsViewModel.callUpdateEvent.observe(
viewLifecycleOwner
) {
it.consume { call ->
if (call.state == Call.State.StreamsRunning) {
dialog?.dismiss()
} else if (call.state == Call.State.UpdatedByRemote) {
if (coreContext.core.isVideoEnabled) {
val remoteVideo = call.remoteParams?.isVideoEnabled ?: false
val localVideo = call.currentParams.isVideoEnabled
if (remoteVideo && !localVideo) {
showCallVideoUpdateDialog(call)
}
} else {
Log.w("[Call] Video display & capture are disabled, don't show video dialog")
}
}
val conference = call.conference
if (conference != null && conferenceViewModel.conference.value == null) {
Log.i("[Call] Found conference attached to call and no conference in dedicated view model, init & configure it")
@ -216,7 +196,7 @@ class ActiveCallOrConferenceFragment : GenericFragment<VoipActiveCallOrConferenc
}
}
controlsViewModel.goToDialer.observe(
controlsViewModel.goToDialerEvent.observe(
viewLifecycleOwner
) {
it.consume { isCallTransfer ->
@ -229,12 +209,6 @@ class ActiveCallOrConferenceFragment : GenericFragment<VoipActiveCallOrConferenc
}
}
val remoteLayout = binding.root.findViewById<LinearLayout>(R.id.remote_layout)
val remoteVideoView = remoteLayout.findViewById<RoundCornersTextureView>(R.id.remote_video_surface)
coreContext.core.nativeVideoWindowId = remoteVideoView
val localVideoView = remoteLayout.findViewById<CaptureTextureView>(R.id.local_preview_video_surface)
coreContext.core.nativePreviewWindowId = localVideoView
binding.stubbedConferenceActiveSpeakerLayout.setOnInflateListener { _, inflated ->
Log.i("[Call] Active speaker conference layout inflated")
val binding = DataBindingUtil.bind<ViewDataBinding>(inflated)
@ -271,16 +245,6 @@ class ActiveCallOrConferenceFragment : GenericFragment<VoipActiveCallOrConferenc
binding?.lifecycleOwner = viewLifecycleOwner
}
binding.stubbedPausedCall.setOnInflateListener { _, inflated ->
val binding = DataBindingUtil.bind<ViewDataBinding>(inflated)
binding?.lifecycleOwner = viewLifecycleOwner
}
binding.stubbedRemotelyPausedCall.setOnInflateListener { _, inflated ->
val binding = DataBindingUtil.bind<ViewDataBinding>(inflated)
binding?.lifecycleOwner = viewLifecycleOwner
}
binding.stubbedPausedConference.setOnInflateListener { _, inflated ->
val binding = DataBindingUtil.bind<ViewDataBinding>(inflated)
binding?.lifecycleOwner = viewLifecycleOwner
@ -293,36 +257,6 @@ class ActiveCallOrConferenceFragment : GenericFragment<VoipActiveCallOrConferenc
controlsViewModel.hideExtraButtons(true)
}
override fun onDestroy() {
super.onDestroy()
coreContext.core.nativeVideoWindowId = null
coreContext.core.nativePreviewWindowId = null
}
private fun showCallVideoUpdateDialog(call: Call) {
val viewModel = DialogViewModel(AppUtils.getString(R.string.call_video_update_requested_dialog))
dialog = DialogUtils.getVoipDialog(requireContext(), viewModel)
viewModel.showCancelButton(
{
coreContext.answerCallVideoUpdateRequest(call, false)
dialog?.dismiss()
},
getString(R.string.dialog_decline)
)
viewModel.showOkButton(
{
coreContext.answerCallVideoUpdateRequest(call, true)
dialog?.dismiss()
},
getString(R.string.dialog_accept)
)
dialog?.show()
}
private fun switchToFullScreenIfPossible(conference: Conference) {
if (corePreferences.enableFullScreenWhenJoiningVideoConference) {
if (conference.currentParams.isVideoEnabled) {
@ -371,4 +305,20 @@ class ActiveCallOrConferenceFragment : GenericFragment<VoipActiveCallOrConferenc
timer.start()
}
private fun updateHingeRelatedConstraints(state: FoldingFeature.State) {
/*val constraintLayout = binding.constraintLayout
val set = ConstraintSet()
set.clone(constraintLayout)
if (state == FoldingFeature.State.HALF_OPENED) {
set.setGuidelinePercent(R.id.hinge_top, 0.5f)
set.setGuidelinePercent(R.id.hinge_bottom, 0.5f)
} else {
set.setGuidelinePercent(R.id.hinge_top, 0f)
set.setGuidelinePercent(R.id.hinge_bottom, 1f)
}
set.applyTo(constraintLayout)*/
}
}

View file

@ -0,0 +1,250 @@
/*
* 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.activities.voip.fragments
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import android.os.SystemClock
import android.view.View
import android.widget.Chronometer
import android.widget.LinearLayout
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.navigation.navGraphViewModels
import androidx.window.layout.FoldingFeature
import com.google.android.material.snackbar.Snackbar
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.activities.*
import org.linphone.activities.main.MainActivity
import org.linphone.activities.main.viewmodels.DialogViewModel
import org.linphone.activities.voip.viewmodels.CallsViewModel
import org.linphone.activities.voip.viewmodels.ConferenceViewModel
import org.linphone.activities.voip.viewmodels.ControlsViewModel
import org.linphone.activities.voip.viewmodels.StatisticsListViewModel
import org.linphone.activities.voip.views.RoundCornersTextureView
import org.linphone.core.*
import org.linphone.core.tools.Log
import org.linphone.databinding.VoipSingleCallFragmentBinding
import org.linphone.mediastream.video.capture.CaptureTextureView
import org.linphone.utils.AppUtils
import org.linphone.utils.DialogUtils
class SingleCallFragment : GenericFragment<VoipSingleCallFragmentBinding>() {
private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph)
private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph)
private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph)
private val statsViewModel: StatisticsListViewModel by navGraphViewModels(R.id.call_nav_graph)
private var dialog: Dialog? = null
override fun getLayoutId(): Int = R.layout.voip_single_call_fragment
override fun onStart() {
useMaterialSharedAxisXForwardAnimation = false
super.onStart()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
controlsViewModel.hideCallStats() // In case it was toggled on during incoming/outgoing fragment was visible
binding.lifecycleOwner = viewLifecycleOwner
binding.controlsViewModel = controlsViewModel
binding.callsViewModel = callsViewModel
binding.conferenceViewModel = conferenceViewModel
binding.statsViewModel = statsViewModel
callsViewModel.currentCallData.observe(
viewLifecycleOwner
) {
if (it != null) {
val timer = binding.root.findViewById<Chronometer>(R.id.active_call_timer)
timer.base =
SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds
timer.start()
}
}
controlsViewModel.goToConferenceParticipantsListEvent.observe(
viewLifecycleOwner
) {
it.consume {
navigateToConferenceParticipants()
}
}
controlsViewModel.goToChatEvent.observe(
viewLifecycleOwner
) {
it.consume {
goToChat()
}
}
controlsViewModel.goToCallsListEvent.observe(
viewLifecycleOwner
) {
it.consume {
navigateToCallsList()
}
}
controlsViewModel.goToConferenceLayoutSettingsEvent.observe(
viewLifecycleOwner
) {
it.consume {
navigateToConferenceLayout()
}
}
controlsViewModel.foldingStateChangedEvent.observe(
viewLifecycleOwner
) {
it.consume { state ->
updateHingeRelatedConstraints(state)
}
}
callsViewModel.callUpdateEvent.observe(
viewLifecycleOwner
) {
it.consume { call ->
if (call.state == Call.State.StreamsRunning) {
dialog?.dismiss()
} else if (call.state == Call.State.UpdatedByRemote) {
if (coreContext.core.isVideoEnabled) {
val remoteVideo = call.remoteParams?.isVideoEnabled ?: false
val localVideo = call.currentParams.isVideoEnabled
if (remoteVideo && !localVideo) {
showCallVideoUpdateDialog(call)
}
} else {
Log.w("[Call] Video display & capture are disabled, don't show video dialog")
}
}
}
}
controlsViewModel.goToDialerEvent.observe(
viewLifecycleOwner
) {
it.consume { isCallTransfer ->
val intent = Intent()
intent.setClass(requireContext(), MainActivity::class.java)
intent.putExtra("Dialer", true)
intent.putExtra("Transfer", isCallTransfer)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}
val remoteLayout = binding.root.findViewById<LinearLayout>(R.id.remote_layout)
val remoteVideoView = remoteLayout.findViewById<RoundCornersTextureView>(R.id.remote_video_surface)
coreContext.core.nativeVideoWindowId = remoteVideoView
val localVideoView = remoteLayout.findViewById<CaptureTextureView>(R.id.local_preview_video_surface)
coreContext.core.nativePreviewWindowId = localVideoView
binding.stubbedAudioRoutes.setOnInflateListener { _, inflated ->
val binding = DataBindingUtil.bind<ViewDataBinding>(inflated)
binding?.lifecycleOwner = viewLifecycleOwner
}
binding.stubbedNumpad.setOnInflateListener { _, inflated ->
val binding = DataBindingUtil.bind<ViewDataBinding>(inflated)
binding?.lifecycleOwner = viewLifecycleOwner
}
binding.stubbedCallStats.setOnInflateListener { _, inflated ->
val binding = DataBindingUtil.bind<ViewDataBinding>(inflated)
binding?.lifecycleOwner = viewLifecycleOwner
}
binding.stubbedPausedCall.setOnInflateListener { _, inflated ->
val binding = DataBindingUtil.bind<ViewDataBinding>(inflated)
binding?.lifecycleOwner = viewLifecycleOwner
}
binding.stubbedRemotelyPausedCall.setOnInflateListener { _, inflated ->
val binding = DataBindingUtil.bind<ViewDataBinding>(inflated)
binding?.lifecycleOwner = viewLifecycleOwner
}
}
override fun onPause() {
super.onPause()
controlsViewModel.hideExtraButtons(true)
}
private fun showCallVideoUpdateDialog(call: Call) {
val viewModel = DialogViewModel(AppUtils.getString(R.string.call_video_update_requested_dialog))
dialog = DialogUtils.getVoipDialog(requireContext(), viewModel)
viewModel.showCancelButton(
{
coreContext.answerCallVideoUpdateRequest(call, false)
dialog?.dismiss()
},
getString(R.string.dialog_decline)
)
viewModel.showOkButton(
{
coreContext.answerCallVideoUpdateRequest(call, true)
dialog?.dismiss()
},
getString(R.string.dialog_accept)
)
dialog?.show()
}
private fun goToChat() {
val intent = Intent()
intent.setClass(requireContext(), MainActivity::class.java)
intent.putExtra("Chat", true)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
private fun updateHingeRelatedConstraints(state: FoldingFeature.State) {
/*val constraintLayout = binding.constraintLayout
val set = ConstraintSet()
set.clone(constraintLayout)
if (state == FoldingFeature.State.HALF_OPENED) {
set.setGuidelinePercent(R.id.hinge_top, 0.5f)
set.setGuidelinePercent(R.id.hinge_bottom, 0.5f)
} else {
set.setGuidelinePercent(R.id.hinge_top, 0f)
set.setGuidelinePercent(R.id.hinge_bottom, 1f)
}
set.applyTo(constraintLayout)*/
}
}

View file

@ -27,6 +27,7 @@ import android.view.animation.LinearInterpolator
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.window.layout.FoldingFeature
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
@ -90,7 +91,7 @@ class ControlsViewModel : ViewModel() {
MutableLiveData<Event<Boolean>>()
}
val goToConferenceLayoutSettings: MutableLiveData<Event<Boolean>> by lazy {
val goToConferenceLayoutSettingsEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
@ -98,10 +99,14 @@ class ControlsViewModel : ViewModel() {
MutableLiveData<Event<String>>()
}
val goToDialer: MutableLiveData<Event<Boolean>> by lazy {
val goToDialerEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val foldingStateChangedEvent: MutableLiveData<Event<FoldingFeature.State>> by lazy {
MutableLiveData<Event<FoldingFeature.State>>()
}
private val nonEarpieceOutputAudioDevice = MutableLiveData<Boolean>()
private var previewX: Float = 0f
@ -405,15 +410,15 @@ class ControlsViewModel : ViewModel() {
}
fun goToConferenceLayout() {
goToConferenceLayoutSettings.value = Event(true)
goToConferenceLayoutSettingsEvent.value = Event(true)
}
fun goToDialerForCallTransfer() {
goToDialer.value = Event(true)
goToDialerEvent.value = Event(true)
}
fun goToDialerForNewCall() {
goToDialer.value = Event(false)
goToDialerEvent.value = Event(false)
}
private fun updateUI() {

View file

@ -33,7 +33,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_fragment"
app:navGraph="@navigation/call_nav_graph"
tools:layout="@layout/voip_active_call_or_conference_fragment" />
tools:layout="@layout/voip_single_call_fragment" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -42,8 +42,8 @@
android:id="@+id/stubbed_conference_active_speaker_layout"
android:inflatedId="@+id/conference_active_speaker_layout"
android:layout="@layout/voip_conference_active_speaker"
android:visibility="@{conferenceViewModel.conferenceActiveSpeakerDisplayMode &amp;&amp; conferenceViewModel.conferenceExists &amp;&amp; !callsViewModel.currentCallData.isActiveAndNotInConference ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{conferenceViewModel.conferenceActiveSpeakerDisplayMode &amp;&amp; conferenceViewModel.conferenceExists &amp;&amp; !callsViewModel.currentCallData.isActiveAndNotInConference ? View.VISIBLE : View.GONE}"
android:visibility="@{conferenceViewModel.conferenceActiveSpeakerDisplayMode ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{conferenceViewModel.conferenceActiveSpeakerDisplayMode ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:conferenceViewModel="@{conferenceViewModel}"
@ -52,8 +52,8 @@
<ViewStub
android:id="@+id/stubbed_conference_grid_layout"
android:layout="@layout/voip_conference_grid"
android:visibility="@{conferenceViewModel.conferenceMosaicDisplayMode &amp;&amp; conferenceViewModel.conferenceExists &amp;&amp; !callsViewModel.currentCallData.isActiveAndNotInConference ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{conferenceViewModel.conferenceMosaicDisplayMode &amp;&amp; conferenceViewModel.conferenceExists &amp;&amp; !callsViewModel.currentCallData.isActiveAndNotInConference ? View.VISIBLE : View.GONE}"
android:visibility="@{conferenceViewModel.conferenceMosaicDisplayMode ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{conferenceViewModel.conferenceMosaicDisplayMode ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:conferenceViewModel="@{conferenceViewModel}"
@ -62,22 +62,13 @@
<ViewStub
android:id="@+id/stubbed_conference_audio_only_layout"
android:layout="@layout/voip_conference_audio_only"
android:visibility="@{conferenceViewModel.conferenceAudioOnlyDisplayMode &amp;&amp; conferenceViewModel.conferenceExists &amp;&amp; !callsViewModel.currentCallData.isActiveAndNotInConference ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{conferenceViewModel.conferenceAudioOnlyDisplayMode &amp;&amp; conferenceViewModel.conferenceExists &amp;&amp; !callsViewModel.currentCallData.isActiveAndNotInConference ? View.VISIBLE : View.GONE}"
android:visibility="@{conferenceViewModel.conferenceAudioOnlyDisplayMode ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{conferenceViewModel.conferenceAudioOnlyDisplayMode ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:conferenceViewModel="@{conferenceViewModel}"
app:controlsViewModel="@{controlsViewModel}"/>
<include
android:id="@+id/remote_layout"
layout="@layout/voip_call"
android:visibility="@{conferenceViewModel.conferenceExists &amp;&amp; !callsViewModel.currentCallData.isActiveAndNotInConference ? View.GONE : View.VISIBLE}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:viewModel="@{callsViewModel.currentCallData}"
app:controlsViewModel="@{controlsViewModel}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<ViewStub
@ -113,8 +104,8 @@
<ViewStub
android:id="@+id/stubbed_paused_conference"
android:layout="@layout/voip_conference_paused"
android:visibility="@{conferenceViewModel.conferenceExists &amp;&amp; conferenceViewModel.isConferenceLocallyPaused &amp;&amp; !callsViewModel.currentCallData.isActiveAndNotInConference ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{conferenceViewModel.conferenceExists &amp;&amp; conferenceViewModel.isConferenceLocallyPaused &amp;&amp; !callsViewModel.currentCallData.isActiveAndNotInConference ? View.VISIBLE : View.GONE}"
android:visibility="@{conferenceViewModel.isConferenceLocallyPaused ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{conferenceViewModel.isConferenceLocallyPaused ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
@ -122,29 +113,6 @@
app:layout_constraintBottom_toTopOf="@id/primary_buttons"
app:conferenceViewModel="@{conferenceViewModel}" />
<ViewStub
android:id="@+id/stubbed_remotely_paused_call"
android:layout="@layout/voip_call_paused_by_remote"
android:visibility="@{callsViewModel.currentCallData.isRemotelyPaused ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{callsViewModel.currentCallData.isRemotelyPaused ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/primary_buttons" />
<ViewStub
android:id="@+id/stubbed_paused_call"
android:layout="@layout/voip_call_paused"
android:visibility="@{callsViewModel.currentCallData.isPaused &amp;&amp; !conferenceViewModel.conferenceExists ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{callsViewModel.currentCallData.isPaused &amp;&amp; !conferenceViewModel.conferenceExists ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/primary_buttons"
app:data="@{callsViewModel.currentCallData}" />
<LinearLayout
android:onClick="@{() -> controlsViewModel.hideExtraButtons(false)}"
android:visibility="@{controlsViewModel.showExtras ? View.VISIBLE : View.GONE, default=gone}"

View file

@ -65,7 +65,7 @@
<ImageView
android:onClick="@{() -> data.switchCamera()}"
android:visibility="@{data.videoEnabled &amp;&amp; data.isSwitchCameraAvailable() ? View.VISIBLE : View.GONE, default=gone}"
android:visibility="@{data.videoEnabled &amp;&amp; data.isSwitchCameraAvailable() &amp;&amp; data.isInConference ? View.VISIBLE : View.GONE, default=gone}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"

View file

@ -97,7 +97,7 @@
<ImageView
android:onClick="@{() -> data.switchCamera()}"
android:visibility="@{data.videoEnabled &amp;&amp; data.isSwitchCameraAvailable() ? View.VISIBLE : View.GONE, default=gone}"
android:visibility="@{data.videoEnabled &amp;&amp; data.isSwitchCameraAvailable() &amp;&amp; data.isInConference ? View.VISIBLE : View.GONE, default=gone}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"

View file

@ -0,0 +1,149 @@
<?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"
xmlns:bind="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<import type="com.google.android.flexbox.FlexDirection" />
<variable
name="controlsViewModel"
type="org.linphone.activities.voip.viewmodels.ControlsViewModel" />
<variable
name="callsViewModel"
type="org.linphone.activities.voip.viewmodels.CallsViewModel" />
<variable
name="conferenceViewModel"
type="org.linphone.activities.voip.viewmodels.ConferenceViewModel" />
<variable
name="statsViewModel"
type="org.linphone.activities.voip.viewmodels.StatisticsListViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/voipBackgroundColor">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@{controlsViewModel.fullScreenMode ? @color/black_color : @color/transparent_color}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/primary_buttons"
android:layout_margin="@{controlsViewModel.fullScreenMode || controlsViewModel.pipMode ? @dimen/voip_remote_margin_full_screen : @dimen/voip_remote_margin, default=@dimen/voip_remote_margin}">
<include
android:id="@+id/remote_layout"
layout="@layout/voip_call"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:viewModel="@{callsViewModel.currentCallData}"
app:controlsViewModel="@{controlsViewModel}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<ViewStub
android:id="@+id/stubbed_audio_routes"
android:layout="@layout/voip_buttons_audio_routes"
android:visibility="@{controlsViewModel.audioRoutesEnabled ? View.VISIBLE : View.GONE}"
app:inflatedVisibility="@{controlsViewModel.audioRoutesEnabled ? View.VISIBLE : View.GONE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginStart="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/primary_buttons"
app:controlsViewModel="@{controlsViewModel}"/>
<include
android:id="@+id/primary_buttons"
android:visibility="@{controlsViewModel.fullScreenMode || controlsViewModel.pipMode ? View.GONE : View.VISIBLE}"
layout="@layout/voip_buttons"
android:layout_width="0dp"
android:layout_height="@dimen/voip_buttons_fragment_size"
android:layout_marginBottom="10dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintWidth_max="@dimen/voip_buttons_max_width"
app:controlsViewModel="@{controlsViewModel}"
app:callsViewModel="@{callsViewModel}" />
<ViewStub
android:id="@+id/stubbed_remotely_paused_call"
android:layout="@layout/voip_call_paused_by_remote"
android:visibility="@{callsViewModel.currentCallData.isRemotelyPaused ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{callsViewModel.currentCallData.isRemotelyPaused ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/primary_buttons" />
<ViewStub
android:id="@+id/stubbed_paused_call"
android:layout="@layout/voip_call_paused"
android:visibility="@{callsViewModel.currentCallData.isPaused ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{callsViewModel.currentCallData.isPaused ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/primary_buttons"
app:data="@{callsViewModel.currentCallData}" />
<LinearLayout
android:onClick="@{() -> controlsViewModel.hideExtraButtons(false)}"
android:visibility="@{controlsViewModel.showExtras ? View.VISIBLE : View.GONE, default=gone}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/voip_translucent_popup_background" />
<include
layout="@layout/voip_buttons_extra"
android:translationY="@{controlsViewModel.extraButtonsMenuTranslateY, default=@dimen/voip_call_extra_buttons_translate_y}"
android:layout_width="0dp"
android:layout_height="@dimen/voip_call_extra_buttons_height"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintWidth_max="@dimen/voip_extra_menu_max_width"
app:controlsViewModel="@{controlsViewModel}"
app:callsViewModel="@{callsViewModel}"
app:conferenceViewModel="@{conferenceViewModel}"/>
<ViewStub
android:id="@+id/stubbed_call_stats"
android:layout="@layout/voip_call_stats"
android:visibility="@{controlsViewModel.callStatsVisible ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{controlsViewModel.callStatsVisible ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:statsViewModel="@{statsViewModel}"
app:controlsViewModel="@{controlsViewModel}"/>
<ViewStub
android:id="@+id/stubbed_numpad"
android:layout="@layout/voip_numpad"
android:visibility="@{controlsViewModel.numpadVisible ? View.VISIBLE : View.GONE, default=gone}"
app:inflatedVisibility="@{controlsViewModel.numpadVisible ? View.VISIBLE : View.GONE}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controlsViewModel="@{controlsViewModel}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View file

@ -3,29 +3,52 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/call_nav_graph"
app:startDestination="@id/activeCallOrConferenceFragment">
app:startDestination="@id/singleCallFragment">
<fragment
android:id="@+id/activeCallOrConferenceFragment"
android:name="org.linphone.activities.voip.fragments.ActiveCallOrConferenceFragment"
tools:layout="@layout/voip_active_call_or_conference_fragment"
android:label="ActiveCallOrConferenceFragment" >
android:id="@+id/singleCallFragment"
android:name="org.linphone.activities.voip.fragments.SingleCallFragment"
tools:layout="@layout/voip_single_call_fragment"
android:label="SingleCallFragment" >
<action
android:id="@+id/action_activeCallOrConferenceFragment_to_callsListFragment"
android:id="@+id/action_singleCallFragment_to_callsListFragment"
app:destination="@id/callsListFragment" />
<action
android:id="@+id/action_activeCallOrConferenceFragment_to_conferenceParticipantsFragment"
android:id="@+id/action_singleCallFragment_to_conferenceParticipantsFragment"
app:destination="@id/conferenceParticipantsFragment" />
<action
android:id="@+id/action_activeCallOrConferenceFragment_to_chatFragment"
android:id="@+id/action_singleCallFragment_to_chatFragment"
app:destination="@id/chatFragment" />
<action
android:id="@+id/action_activeCallOrConferenceFragment_to_conferenceLayoutFragment"
android:id="@+id/action_singleCallFragment_to_conferenceLayoutFragment"
app:destination="@id/conferenceLayoutFragment" />
</fragment>
<action android:id="@+id/action_global_activeCallOrConferenceFragment"
app:destination="@id/activeCallOrConferenceFragment"/>
<action android:id="@+id/action_global_singleCallFragment"
app:destination="@id/singleCallFragment"/>
<fragment
android:id="@+id/conferenceCallFragment"
android:name="org.linphone.activities.voip.fragments.ConferenceCallFragment"
tools:layout="@layout/voip_conference_call_fragment"
android:label="ConferenceCallFragment" >
<action
android:id="@+id/action_conferenceCallFragment_to_callsListFragment"
app:destination="@id/callsListFragment" />
<action
android:id="@+id/action_conferenceCallFragment_to_conferenceParticipantsFragment"
app:destination="@id/conferenceParticipantsFragment" />
<action
android:id="@+id/action_conferenceCallFragment_to_chatFragment"
app:destination="@id/chatFragment" />
<action
android:id="@+id/action_conferenceCallFragment_to_conferenceLayoutFragment"
app:destination="@id/conferenceLayoutFragment" />
</fragment>
<action
android:id="@+id/action_global_conferenceCallFragment"
app:destination="@id/conferenceCallFragment"/>
<fragment
android:id="@+id/callsListFragment"