Added attented transfer + setting to skip dialer when adding new call or transfering a call + fixed toast instead of snack for dialer transfer message

This commit is contained in:
Sylvain Berfini 2023-03-14 17:33:35 +01:00
parent 74fd59a541
commit 09bde054d0
12 changed files with 109 additions and 26 deletions

View file

@ -15,6 +15,7 @@ Group changes to describe their impact on the project, as follows:
### Added
- Showing short term presence for contacts whom publish it + added setting to disable it (enabled by default for sip.linphone.org accounts)
- Confirmation dialog before removing account
- Attended transfer instead of blind transfer if there is more than 1 call
### Changed
- Account EXPIRES is now set to 1 month instead of 1 year for sip.linphone.org accounts

View file

@ -66,7 +66,7 @@ fun popupTo(
/* Main activity related */
internal fun MainActivity.navigateToDialer(args: Bundle?) {
internal fun MainActivity.navigateToDialer(args: Bundle? = null) {
findNavController(R.id.nav_host_fragment).navigate(
R.id.action_global_dialerFragment,
args,
@ -90,6 +90,14 @@ internal fun MainActivity.navigateToChatRoom(localAddress: String?, peerAddress:
)
}
internal fun MainActivity.navigateToContacts() {
findNavController(R.id.nav_host_fragment).navigate(
R.id.action_global_masterContactsFragment,
null,
popupTo(R.id.masterContactsFragment, true)
)
}
internal fun MainActivity.navigateToContact(contactId: String?) {
val deepLink = "linphone-android://contact/view/$contactId"
findNavController(R.id.nav_host_fragment).navigate(

View file

@ -339,9 +339,15 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
}
intent.hasExtra("Dialer") -> {
Log.i("[Main Activity] Found dialer intent extra, go to dialer")
val args = Bundle()
args.putBoolean("Transfer", intent.getBooleanExtra("Transfer", false))
navigateToDialer(args)
val isTransfer = intent.getBooleanExtra("Transfer", false)
sharedViewModel.pendingCallTransfer = isTransfer
navigateToDialer()
}
intent.hasExtra("Contacts") -> {
Log.i("[Main Activity] Found contacts intent extra, go to contacts list")
val isTransfer = intent.getBooleanExtra("Transfer", false)
sharedViewModel.pendingCallTransfer = isTransfer
navigateToContacts()
}
else -> {
val core = coreContext.core

View file

@ -31,7 +31,6 @@ import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis
@ -152,8 +151,8 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
viewModel.onMessageToNotifyEvent.observe(
viewLifecycleOwner
) {
it.consume { id ->
Toast.makeText(requireContext(), id, Toast.LENGTH_SHORT).show()
it.consume { resourceId ->
(requireActivity() as MainActivity).showSnackBar(resourceId)
}
}
@ -162,18 +161,21 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
return
}
if (arguments?.containsKey("Transfer") == true) {
sharedViewModel.pendingCallTransfer = arguments?.getBoolean("Transfer") ?: false
Log.i("[Dialer] Is pending call transfer: ${sharedViewModel.pendingCallTransfer}")
}
if (arguments?.containsKey("URI") == true) {
val address = arguments?.getString("URI") ?: ""
Log.i("[Dialer] Found URI to call: $address")
val skipAutoCall = arguments?.getBoolean("SkipAutoCallStart") ?: false
if (corePreferences.callRightAway && !skipAutoCall) {
Log.i("[Dialer] Call right away setting is enabled, start the call to $address")
if (corePreferences.skipDialerForNewCallAndTransfer) {
if (sharedViewModel.pendingCallTransfer) {
Log.i("[Dialer] We were asked to skip dialer so starting new call to [$address] now")
viewModel.transferCallTo(address)
} else {
Log.i("[Dialer] We were asked to skip dialer so starting transfer to [$address] now")
viewModel.directCall(address)
}
} else if (corePreferences.callRightAway && !skipAutoCall) {
Log.i("[Dialer] Call right away setting is enabled, start the call to [$address]")
viewModel.directCall(address)
} else {
sharedViewModel.dialerUri = address
@ -184,6 +186,8 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
Log.i("[Dialer] Pending call transfer mode = ${sharedViewModel.pendingCallTransfer}")
viewModel.transferVisibility.value = sharedViewModel.pendingCallTransfer
viewModel.autoInitiateVideoCalls.value = coreContext.core.videoActivationPolicy.automaticallyInitiate
checkForUpdate()
checkPermissions()

View file

@ -98,6 +98,13 @@ class DialerViewModel : LogsUploadViewModel() {
atLeastOneCall.value = core.callsNb > 0
}
override fun onTransferStateChanged(core: Core, transfered: Call, callState: Call.State) {
if (callState == Call.State.OutgoingProgress) {
// Will work for both blind & attended transfer
onMessageToNotifyEvent.value = Event(org.linphone.R.string.dialer_transfer_succeded)
}
}
override fun onNetworkReachable(core: Core, reachable: Boolean) {
val address = addressWaitingNetworkToBeCalled.orEmpty()
if (reachable && address.isNotEmpty()) {
@ -207,13 +214,7 @@ class DialerViewModel : LogsUploadViewModel() {
fun transferCall(): Boolean {
val addressToCall = enteredUri.value.orEmpty()
return if (addressToCall.isNotEmpty()) {
onMessageToNotifyEvent.value = Event(
if (coreContext.transferCallTo(addressToCall)) {
org.linphone.R.string.dialer_transfer_succeded
} else {
org.linphone.R.string.dialer_transfer_failed
}
)
transferCallTo(addressToCall)
eraseAll()
true
} else {
@ -222,6 +223,12 @@ class DialerViewModel : LogsUploadViewModel() {
}
}
fun transferCallTo(addressToCall: String) {
if (!coreContext.transferCallTo(addressToCall)) {
onMessageToNotifyEvent.value = Event(org.linphone.R.string.dialer_transfer_failed)
}
}
fun switchCamera() {
coreContext.switchCamera()
}

View file

@ -249,7 +249,11 @@ class ConferenceCallFragment : GenericFragment<VoipConferenceCallFragmentBinding
it.consume { isCallTransfer ->
val intent = Intent()
intent.setClass(requireContext(), MainActivity::class.java)
intent.putExtra("Dialer", true)
if (corePreferences.skipDialerForNewCallAndTransfer) {
intent.putExtra("Contacts", true)
} else {
intent.putExtra("Dialer", true)
}
intent.putExtra("Transfer", isCallTransfer)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)

View file

@ -161,7 +161,11 @@ class SingleCallFragment : GenericVideoPreviewFragment<VoipSingleCallFragmentBin
it.consume { isCallTransfer ->
val intent = Intent()
intent.setClass(requireContext(), MainActivity::class.java)
intent.putExtra("Dialer", true)
if (corePreferences.skipDialerForNewCallAndTransfer) {
intent.putExtra("Contacts", true)
} else {
intent.putExtra("Dialer", true)
}
intent.putExtra("Transfer", isCallTransfer)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)

View file

@ -79,6 +79,8 @@ class ControlsViewModel : ViewModel() {
val showTakeSnapshotButton = MutableLiveData<Boolean>()
val attendedTransfer = MutableLiveData<Boolean>()
val goToConferenceParticipantsListEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
@ -117,6 +119,7 @@ class ControlsViewModel : ViewModel() {
Log.i("[Call Controls] State changed: $state")
isOutgoingEarlyMedia.value = state == Call.State.OutgoingEarlyMedia
isIncomingEarlyMediaVideo.value = state == Call.State.IncomingEarlyMedia && call.remoteParams?.isVideoEnabled == true
attendedTransfer.value = core.callsNb > 1
if (state == Call.State.StreamsRunning) {
if (!call.currentParams.isVideoEnabled && fullScreenMode.value == true) {
@ -412,7 +415,44 @@ class ControlsViewModel : ViewModel() {
goToConferenceLayoutSettingsEvent.value = Event(true)
}
fun goToDialerForCallTransfer() {
fun transferCall() {
// In case there is more than 1 call, transfer will be attended instead of blind
if (coreContext.core.callsNb > 1) {
attendedTransfer()
} else {
goToDialerForCallTransfer()
}
}
private fun attendedTransfer() {
val core = coreContext.core
val currentCall = core.currentCall
if (currentCall == null) {
Log.e("[Call Controls] Can't do an attended transfer without a current call")
return
}
if (core.callsNb <= 1) {
Log.e("[Call Controls] Need at least two calls to do an attended transfer")
return
}
val callToTransferTo = core.calls.findLast {
it.state == Call.State.Paused
}
if (callToTransferTo == null) {
Log.e("[Call Controls] Couldn't find a call in Paused state to transfer current call to")
return
}
Log.i("[Call Controls] Doing an attended transfer between active call [${currentCall.remoteAddress.asStringUriOnly()}] and paused call [${callToTransferTo.remoteAddress.asStringUriOnly()}]")
val result = callToTransferTo.transferToAnother(currentCall)
if (result != 0) {
Log.e("[Call Controls] Attended transfer failed!")
}
}
private fun goToDialerForCallTransfer() {
goToDialerEvent.value = Event(true)
}

View file

@ -317,6 +317,13 @@ class CorePreferences constructor(private val context: Context) {
config.setBool("app", "call_right_away", value)
}
// Will send user to contacts list directly
var skipDialerForNewCallAndTransfer: Boolean
get() = config.getBool("app", "skip_dialer_for_new_call_and_transfer", false)
set(value) {
config.setBool("app", "skip_dialer_for_new_call_and_transfer", value)
}
var automaticallyStartCallRecording: Boolean
get() = config.getBool("app", "auto_start_call_record", false)
set(value) {

View file

@ -108,8 +108,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:onClick="@{() -> controlsViewModel.goToDialerForCallTransfer()}"
android:text="@string/call_action_transfer_call"
android:onClick="@{() -> controlsViewModel.transferCall()}"
android:text="@{controlsViewModel.attendedTransfer ? @string/call_action_attended_transfer_call : @string/call_action_transfer_call, default=@string/call_action_transfer_call}"
android:visibility="@{conferenceViewModel.conferenceExists ? View.GONE : View.VISIBLE, default=gone}"
app:drawableTopCompat="@drawable/icon_call_forward"
app:layout_constraintBottom_toBottomOf="parent"

View file

@ -768,4 +768,5 @@
<string name="account_setting_delete_dialog_title">Voulez-vous supprimer votre compte ?</string>
<string name="account_setting_delete_generic_confirmation_dialog">Votre compte sera supprimé localement.\nPour le supprimer de manière définitive, rendez-vous sur le site internet de votre fournisseur SIP.</string>
<string name="account_setting_delete_sip_linphone_org_confirmation_dialog">Votre compte sera supprimé localement.\nPour le supprimer de manière définitive, rendez-vous sur notre plateforme de gestion des comptes :</string>
<string name="call_action_attended_transfer_call">Transfert supervisé</string>
</resources>

View file

@ -348,6 +348,7 @@
<string name="call_action_statistics">Call statistics</string>
<string name="call_action_add_call">Start new call</string>
<string name="call_action_transfer_call">Transfer call</string>
<string name="call_action_attended_transfer_call">Attended transfer</string>
<string name="call_context_action_resume">Resume call</string>
<string name="call_context_action_pause">Pause call</string>
<string name="call_context_action_transfer">Transfer call</string>