diff --git a/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt index ea5f9ad57..e9b9a2691 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt @@ -19,16 +19,23 @@ */ package org.linphone.ui.call.fragment +import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.annotation.UiThread import androidx.lifecycle.ViewModelProvider import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R import org.linphone.core.tools.Log import org.linphone.databinding.CallIncomingFragmentBinding import org.linphone.ui.call.viewmodel.CurrentCallViewModel +import org.linphone.utils.AppUtils +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min @UiThread class IncomingCallFragment : GenericCallFragment() { @@ -40,6 +47,66 @@ class IncomingCallFragment : GenericCallFragment() { private lateinit var callViewModel: CurrentCallViewModel + private val marginSize = AppUtils.getDimension(R.dimen.sliding_accept_decline_call_margin) + private val areaSize = AppUtils.getDimension(R.dimen.call_button_size) + marginSize + private var initialX = 0f + private var slidingButtonX = 0f + private val slidingButtonTouchListener = View.OnTouchListener { view, event -> + val width = binding.bottomBar.root.width.toFloat() + val aboveAnswer = view.x + view.width > width - areaSize + val aboveDecline = view.x < areaSize + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + if (initialX == 0f) { + initialX = view.x + } + slidingButtonX = view.x - event.rawX + true + } + MotionEvent.ACTION_UP -> { + if (aboveAnswer) { + // Accept + callViewModel.answer() + } else if (aboveDecline) { + // Decline + callViewModel.hangUp() + } else { + // Animate going back to initial position + view.animate() + .x(initialX) + .setDuration(500) + .start() + } + true + } + MotionEvent.ACTION_MOVE -> { + callViewModel.slidingButtonAboveAnswer.value = aboveAnswer + callViewModel.slidingButtonAboveDecline.value = aboveDecline + + val offset = view.x - initialX + val percent = abs(offset) / (width / 2) + if (offset > 0) { + callViewModel.answerAlpha.value = 1f + callViewModel.declineAlpha.value = 1f - percent + } else if (offset < 0) { + callViewModel.answerAlpha.value = 1f - percent + callViewModel.declineAlpha.value = 1f + } + + view.animate() + .x(min(max(marginSize, event.rawX + slidingButtonX), width - view.width - marginSize)) + .setDuration(0) + .start() + true + } + else -> { + view.performClick() + false + } + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -49,6 +116,7 @@ class IncomingCallFragment : GenericCallFragment() { return binding.root } + @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -68,11 +136,14 @@ class IncomingCallFragment : GenericCallFragment() { } } } + + binding.bottomBar.slidingButton.setOnTouchListener(slidingButtonTouchListener) } override fun onResume() { super.onResume() + callViewModel.refreshKeyguardLockedStatus() coreContext.notificationsManager.setIncomingCallFragmentCurrentlyDisplayed(true) } diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index 0292cd7e8..a93241988 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -20,6 +20,8 @@ package org.linphone.ui.call.viewmodel import android.Manifest +import android.app.KeyguardManager +import android.content.Context import android.content.pm.PackageManager import androidx.annotation.AnyThread import androidx.annotation.UiThread @@ -249,6 +251,18 @@ class CurrentCallViewModel MutableLiveData>() } + // Sliding answer/decline button + + val isScreenLocked = MutableLiveData() + + val slidingButtonAboveAnswer = MutableLiveData() + + val slidingButtonAboveDecline = MutableLiveData() + + val answerAlpha = MutableLiveData() + + val declineAlpha = MutableLiveData() + lateinit var currentCall: Call private val contactsListener = object : ContactsListener { @@ -508,38 +522,16 @@ class CurrentCallViewModel } } - @WorkerThread - private fun updateProximitySensor() { - if (::currentCall.isInitialized) { - val callState = currentCall.state - if (LinphoneUtils.isCallIncoming(callState)) { - proximitySensorEnabled.postValue(false) - } else if (LinphoneUtils.isCallOutgoing(callState)) { - val videoEnabled = currentCall.params.isVideoEnabled - proximitySensorEnabled.postValue(!videoEnabled) - } else { - if (isSendingVideo.value == true || isReceivingVideo.value == true) { - proximitySensorEnabled.postValue(false) - } else { - val outputAudioDevice = currentCall.outputAudioDevice ?: coreContext.core.outputAudioDevice - if (outputAudioDevice != null && outputAudioDevice.type == AudioDevice.Type.Earpiece) { - proximitySensorEnabled.postValue(true) - } else { - proximitySensorEnabled.postValue(false) - } - } - } - } else { - proximitySensorEnabled.postValue(false) - } - } - init { fullScreenMode.value = false operationInProgress.value = false proximitySensorEnabled.value = false videoUpdateInProgress.value = false + refreshKeyguardLockedStatus() + answerAlpha.value = 1f + declineAlpha.value = 1f + coreContext.postOnCoreThread { core -> coreContext.contactsManager.addListener(contactsListener) @@ -602,6 +594,14 @@ class CurrentCallViewModel } } + @UiThread + fun refreshKeyguardLockedStatus() { + val keyguardManager = coreContext.context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val secure = keyguardManager.isKeyguardLocked + isScreenLocked.value = secure + Log.i("$TAG Device is [${if (secure) "locked" else "unlocked"}]") + } + @UiThread fun answer() { coreContext.postOnCoreThread { core -> @@ -1511,4 +1511,30 @@ class CurrentCallViewModel private fun showRecordingToast() { showGreenToast(R.string.call_is_being_recorded, R.drawable.record_fill) } + + @WorkerThread + private fun updateProximitySensor() { + if (::currentCall.isInitialized) { + val callState = currentCall.state + if (LinphoneUtils.isCallIncoming(callState)) { + proximitySensorEnabled.postValue(false) + } else if (LinphoneUtils.isCallOutgoing(callState)) { + val videoEnabled = currentCall.params.isVideoEnabled + proximitySensorEnabled.postValue(!videoEnabled) + } else { + if (isSendingVideo.value == true || isReceivingVideo.value == true) { + proximitySensorEnabled.postValue(false) + } else { + val outputAudioDevice = currentCall.outputAudioDevice ?: coreContext.core.outputAudioDevice + if (outputAudioDevice != null && outputAudioDevice.type == AudioDevice.Type.Earpiece) { + proximitySensorEnabled.postValue(true) + } else { + proximitySensorEnabled.postValue(false) + } + } + } + } else { + proximitySensorEnabled.postValue(false) + } + } } diff --git a/app/src/main/res/drawable/arrow_green.xml b/app/src/main/res/drawable/arrow_green.xml new file mode 100644 index 000000000..27f31c09c --- /dev/null +++ b/app/src/main/res/drawable/arrow_green.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/arrow_red.xml b/app/src/main/res/drawable/arrow_red.xml new file mode 100644 index 000000000..6fd056270 --- /dev/null +++ b/app/src/main/res/drawable/arrow_red.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/call_incoming_actions.xml b/app/src/main/res/layout/call_incoming_actions.xml index 015b0a7b3..8346e38ae 100644 --- a/app/src/main/res/layout/call_incoming_actions.xml +++ b/app/src/main/res/layout/call_incoming_actions.xml @@ -14,6 +14,146 @@ android:layout_height="@dimen/call_main_actions_menu_height" android:background="@drawable/shape_call_bottom_sheet_background"> + + + + + + + + + + + + + + + + + + + + + + - - diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 8d99aa567..7e68dc955 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -65,6 +65,7 @@ 20dp 5dp 55dp + 10dp 15dp 65dp 30dp