diff --git a/CHANGELOG.md b/CHANGELOG.md index 98bbc518d..e937291b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt index f5515890a..460469a33 100644 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ b/app/src/main/java/org/linphone/activities/Navigation.kt @@ -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( diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index d625d199b..fc954b82a 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -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 diff --git a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt b/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt index 94a7185fc..5def49c3a 100644 --- a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt @@ -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() { 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() { 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() { Log.i("[Dialer] Pending call transfer mode = ${sharedViewModel.pendingCallTransfer}") viewModel.transferVisibility.value = sharedViewModel.pendingCallTransfer + viewModel.autoInitiateVideoCalls.value = coreContext.core.videoActivationPolicy.automaticallyInitiate + checkForUpdate() checkPermissions() diff --git a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt index f299bf73f..393026771 100644 --- a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt @@ -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() } diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceCallFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceCallFragment.kt index 26d35549c..f431eea99 100644 --- a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceCallFragment.kt +++ b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceCallFragment.kt @@ -249,7 +249,11 @@ class ConferenceCallFragment : GenericFragment 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) diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/SingleCallFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/SingleCallFragment.kt index 4af5d65e2..bdbf92075 100644 --- a/app/src/main/java/org/linphone/activities/voip/fragments/SingleCallFragment.kt +++ b/app/src/main/java/org/linphone/activities/voip/fragments/SingleCallFragment.kt @@ -161,7 +161,11 @@ class SingleCallFragment : GenericVideoPreviewFragment 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) diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt index dd6bb4d98..ec4c7ab2c 100644 --- a/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt @@ -79,6 +79,8 @@ class ControlsViewModel : ViewModel() { val showTakeSnapshotButton = MutableLiveData() + val attendedTransfer = MutableLiveData() + val goToConferenceParticipantsListEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -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) } diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index b88579935..838d5a30d 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -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) { diff --git a/app/src/main/res/layout/voip_buttons_extra.xml b/app/src/main/res/layout/voip_buttons_extra.xml index 2c3cc4a12..4e22786c2 100644 --- a/app/src/main/res/layout/voip_buttons_extra.xml +++ b/app/src/main/res/layout/voip_buttons_extra.xml @@ -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" diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 07721a1a7..3e1a600e4 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -768,4 +768,5 @@ Voulez-vous supprimer votre compte ? Votre compte sera supprimé localement.\nPour le supprimer de manière définitive, rendez-vous sur le site internet de votre fournisseur SIP. Votre compte sera supprimé localement.\nPour le supprimer de manière définitive, rendez-vous sur notre plateforme de gestion des comptes : + Transfert supervisé \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index abf90cdeb..36ded6322 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -348,6 +348,7 @@ Call statistics Start new call Transfer call + Attended transfer Resume call Pause call Transfer call