Factorized code to handle local video preview moving (with camera switch)

This commit is contained in:
Sylvain Berfini 2022-07-15 11:07:30 +02:00
parent 1bb294acf1
commit 30d6e556c1
11 changed files with 173 additions and 56 deletions

View file

@ -27,21 +27,21 @@ import android.view.View
import android.widget.PopupWindow
import androidx.databinding.DataBindingUtil
import androidx.navigation.navGraphViewModels
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.activities.GenericFragment
import org.linphone.activities.main.MainActivity
import org.linphone.activities.voip.ConferenceDisplayMode
import org.linphone.activities.voip.data.CallData
import org.linphone.activities.voip.viewmodels.CallsViewModel
import org.linphone.activities.voip.viewmodels.ConferenceViewModel
import org.linphone.activities.voip.viewmodels.ControlsViewModel
import org.linphone.databinding.VoipCallContextMenuBindingImpl
import org.linphone.databinding.VoipCallsListFragmentBinding
import org.linphone.utils.AppUtils
class CallsListFragment : GenericFragment<VoipCallsListFragmentBinding>() {
class CallsListFragment : GenericVideoPreviewFragment<VoipCallsListFragmentBinding>() {
private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph)
private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph)
private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph)
override fun getLayoutId(): Int = R.layout.voip_calls_list_fragment
@ -57,8 +57,13 @@ class CallsListFragment : GenericFragment<VoipCallsListFragmentBinding>() {
binding.lifecycleOwner = viewLifecycleOwner
binding.callsViewModel = callsViewModel
binding.conferenceViewModel = conferenceViewModel
binding.controlsViewModel = controlsViewModel
setupLocalViewPreview(binding.localPreviewVideoSurface, binding.switchCamera)
binding.setCancelClickListener {
goBack()
}
@ -91,12 +96,6 @@ class CallsListFragment : GenericFragment<VoipCallsListFragmentBinding>() {
}
}
override fun onResume() {
super.onResume()
coreContext.core.nativePreviewWindowId = binding.localPreviewVideoSurface
}
private fun showCallMenu(anchor: View, callData: CallData) {
val popupView: VoipCallContextMenuBindingImpl = DataBindingUtil.inflate(
LayoutInflater.from(requireContext()),

View file

@ -25,13 +25,14 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.navigation.navGraphViewModels
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.activities.GenericFragment
import org.linphone.activities.voip.ConferenceDisplayMode
import org.linphone.activities.voip.viewmodels.ConferenceViewModel
import org.linphone.activities.voip.viewmodels.ControlsViewModel
import org.linphone.databinding.VoipConferenceLayoutFragmentBinding
class ConferenceLayoutFragment : GenericFragment<VoipConferenceLayoutFragmentBinding>() {
class ConferenceLayoutFragment : GenericVideoPreviewFragment<VoipConferenceLayoutFragmentBinding>() {
private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph)
private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph)
override fun getLayoutId(): Int = R.layout.voip_conference_layout_fragment
@ -42,6 +43,10 @@ class ConferenceLayoutFragment : GenericFragment<VoipConferenceLayoutFragmentBin
binding.conferenceViewModel = conferenceViewModel
binding.controlsViewModel = controlsViewModel
setupLocalViewPreview(binding.localPreviewVideoSurface, binding.switchCamera)
binding.setCancelClickListener {
goBack()
}

View file

@ -25,14 +25,15 @@ import android.widget.Toast
import androidx.navigation.navGraphViewModels
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.activities.GenericFragment
import org.linphone.activities.navigateToAddParticipants
import org.linphone.activities.voip.viewmodels.ConferenceViewModel
import org.linphone.activities.voip.viewmodels.ControlsViewModel
import org.linphone.core.tools.Log
import org.linphone.databinding.VoipConferenceParticipantsFragmentBinding
class ConferenceParticipantsFragment : GenericFragment<VoipConferenceParticipantsFragmentBinding>() {
class ConferenceParticipantsFragment : GenericVideoPreviewFragment<VoipConferenceParticipantsFragmentBinding>() {
private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph)
private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph)
// Only display events happening during while this fragment is visible
private var skipEvents = true
@ -46,6 +47,10 @@ class ConferenceParticipantsFragment : GenericFragment<VoipConferenceParticipant
binding.conferenceViewModel = conferenceViewModel
binding.controlsViewModel = controlsViewModel
setupLocalViewPreview(binding.localPreviewVideoSurface, binding.switchCamera)
conferenceViewModel.conferenceExists.observe(
viewLifecycleOwner
) { exists ->

View file

@ -0,0 +1,83 @@
/*
* 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.view.MotionEvent
import android.view.TextureView
import android.view.View
import android.widget.ImageView
import androidx.databinding.ViewDataBinding
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.activities.GenericFragment
abstract class GenericVideoPreviewFragment<T : ViewDataBinding> : GenericFragment<T>() {
private var previewX: Float = 0f
private var previewY: Float = 0f
private var switchX: Float = 0f
private var switchY: Float = 0f
private var switchCameraImageView: ImageView? = null
private lateinit var videoPreviewTextureView: TextureView
private val previewTouchListener = View.OnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
previewX = view.x - event.rawX
previewY = view.y - event.rawY
switchX = (switchCameraImageView?.x ?: 0f) - event.rawX
switchY = (switchCameraImageView?.y ?: 0f) - event.rawY
true
}
MotionEvent.ACTION_MOVE -> {
view.animate()
.x(event.rawX + previewX)
.y(event.rawY + previewY)
.setDuration(0)
.start()
switchCameraImageView?.apply {
animate()
.x(event.rawX + switchX)
.y(event.rawY + switchY)
.setDuration(0)
.start()
}
true
}
else -> {
view.performClick()
false
}
}
}
protected fun setupLocalViewPreview(localVideoPreview: TextureView, switchCamera: ImageView?) {
videoPreviewTextureView = localVideoPreview
switchCameraImageView = switchCamera
videoPreviewTextureView.setOnTouchListener(previewTouchListener)
}
override fun onResume() {
super.onResume()
if (::videoPreviewTextureView.isInitialized) {
coreContext.core.nativePreviewWindowId = videoPreviewTextureView
}
}
}

View file

@ -28,13 +28,12 @@ import androidx.databinding.ViewDataBinding
import androidx.navigation.navGraphViewModels
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.activities.GenericFragment
import org.linphone.activities.navigateToActiveCall
import org.linphone.activities.voip.viewmodels.CallsViewModel
import org.linphone.activities.voip.viewmodels.ControlsViewModel
import org.linphone.databinding.VoipCallOutgoingFragmentBinding
class OutgoingCallFragment : GenericFragment<VoipCallOutgoingFragmentBinding>() {
class OutgoingCallFragment : GenericVideoPreviewFragment<VoipCallOutgoingFragmentBinding>() {
private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph)
private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph)
@ -49,6 +48,8 @@ class OutgoingCallFragment : GenericFragment<VoipCallOutgoingFragmentBinding>()
binding.callsViewModel = callsViewModel
setupLocalViewPreview(binding.localPreviewVideoSurface, binding.switchCamera)
callsViewModel.callConnectedEvent.observe(
viewLifecycleOwner
) {

View file

@ -23,7 +23,6 @@ import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import android.os.SystemClock
import android.view.MotionEvent
import android.view.View
import android.widget.Chronometer
import androidx.constraintlayout.widget.ConstraintSet
@ -46,7 +45,7 @@ import org.linphone.databinding.VoipSingleCallFragmentBinding
import org.linphone.utils.AppUtils
import org.linphone.utils.DialogUtils
class SingleCallFragment : GenericFragment<VoipSingleCallFragmentBinding>() {
class SingleCallFragment : GenericVideoPreviewFragment<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)
@ -54,39 +53,6 @@ class SingleCallFragment : GenericFragment<VoipSingleCallFragmentBinding>() {
private var dialog: Dialog? = null
private var previewX: Float = 0f
private var previewY: Float = 0f
private var switchX: Float = 0f
private var switchY: Float = 0f
private val previewTouchListener = View.OnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
previewX = view.x - event.rawX
previewY = view.y - event.rawY
switchX = binding.switchCamera.x - event.rawX
switchY = binding.switchCamera.y - event.rawY
true
}
MotionEvent.ACTION_MOVE -> {
view.animate()
.x(event.rawX + previewX)
.y(event.rawY + previewY)
.setDuration(0)
.start()
binding.switchCamera.animate()
.x(event.rawX + switchX)
.y(event.rawY + switchY)
.setDuration(0)
.start()
true
}
else -> {
view.performClick()
false
}
}
}
override fun getLayoutId(): Int = R.layout.voip_single_call_fragment
override fun onStart() {
@ -102,7 +68,7 @@ class SingleCallFragment : GenericFragment<VoipSingleCallFragmentBinding>() {
binding.lifecycleOwner = viewLifecycleOwner
binding.previewTouchListener = previewTouchListener
setupLocalViewPreview(binding.localPreviewVideoSurface, binding.switchCamera)
binding.controlsViewModel = controlsViewModel

View file

@ -140,6 +140,19 @@
app:layout_constraintHeight_max="200dp"
app:layout_constraintWidth_max="200dp" />
<ImageView
android:id="@+id/switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:contentDescription="@string/content_description_switch_camera"
android:onClick="@{() -> controlsViewModel.switchCamera()}"
android:padding="10dp"
android:src="@drawable/icon_call_camera_switch"
android:visibility="@{controlsViewModel.isSwitchCameraAvailable &amp;&amp; !controlsViewModel.pipMode ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintEnd_toEndOf="@id/local_preview_video_surface"
app:layout_constraintTop_toTopOf="@id/local_preview_video_surface" />
<ViewStub
android:id="@+id/stub_numpad"
android:layout_width="match_parent"

View file

@ -16,6 +16,9 @@
<variable
name="conferenceViewModel"
type="org.linphone.activities.voip.viewmodels.ConferenceViewModel" />
<variable
name="controlsViewModel"
type="org.linphone.activities.voip.viewmodels.ControlsViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -123,6 +126,19 @@
app:layout_constraintHeight_max="200dp"
app:layout_constraintWidth_max="200dp" />
<ImageView
android:id="@+id/switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:contentDescription="@string/content_description_switch_camera"
android:onClick="@{() -> controlsViewModel.switchCamera()}"
android:padding="10dp"
android:src="@drawable/icon_call_camera_switch"
android:visibility="@{controlsViewModel.isSwitchCameraAvailable &amp;&amp; !controlsViewModel.pipMode ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintEnd_toEndOf="@id/local_preview_video_surface"
app:layout_constraintTop_toTopOf="@id/local_preview_video_surface" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -19,6 +19,10 @@
<variable
name="conferenceViewModel"
type="org.linphone.activities.voip.viewmodels.ConferenceViewModel" />
<variable
name="controlsViewModel"
type="org.linphone.activities.voip.viewmodels.ControlsViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -136,6 +140,19 @@
app:layout_constraintHeight_max="200dp"
app:layout_constraintWidth_max="200dp" />
<ImageView
android:id="@+id/switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:contentDescription="@string/content_description_switch_camera"
android:onClick="@{() -> controlsViewModel.switchCamera()}"
android:padding="10dp"
android:src="@drawable/icon_call_camera_switch"
android:visibility="@{controlsViewModel.isSwitchCameraAvailable &amp;&amp; !controlsViewModel.pipMode ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintEnd_toEndOf="@id/local_preview_video_surface"
app:layout_constraintTop_toTopOf="@id/local_preview_video_surface" />
<include
android:id="@+id/too_many_participants_dialog"
layout="@layout/voip_dialog_info"

View file

@ -17,6 +17,10 @@
<variable
name="conferenceViewModel"
type="org.linphone.activities.voip.viewmodels.ConferenceViewModel" />
<variable
name="controlsViewModel"
type="org.linphone.activities.voip.viewmodels.ControlsViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -99,6 +103,19 @@
app:layout_constraintHeight_max="200dp"
app:layout_constraintWidth_max="200dp" />
<ImageView
android:id="@+id/switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:contentDescription="@string/content_description_switch_camera"
android:onClick="@{() -> controlsViewModel.switchCamera()}"
android:padding="10dp"
android:src="@drawable/icon_call_camera_switch"
android:visibility="@{controlsViewModel.isSwitchCameraAvailable &amp;&amp; !controlsViewModel.pipMode ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintEnd_toEndOf="@id/local_preview_video_surface"
app:layout_constraintTop_toTopOf="@id/local_preview_video_surface" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -9,10 +9,6 @@
<import type="com.google.android.flexbox.FlexDirection" />
<variable
name="previewTouchListener"
type="android.view.View.OnTouchListener" />
<variable
name="controlsViewModel"
type="org.linphone.activities.voip.viewmodels.ControlsViewModel" />
@ -234,7 +230,6 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_size="@{controlsViewModel.pipMode ? @dimen/video_preview_pip_max_size : @dimen/video_preview_max_size}"
android:onTouch="@{previewTouchListener}"
app:alignTopRight="true"
app:displayMode="black_bars"
app:layout_constraintBottom_toTopOf="@id/bottom_barrier"