From 433e28e9454c6979762ff5157ecc0e81f4cb66f2 Mon Sep 17 00:00:00 2001 From: "benoit.martins" Date: Thu, 25 Jan 2024 15:49:28 +0100 Subject: [PATCH] Add transfer call and attended transfer --- Linphone/Core/CoreContext.swift | 16 +++ Linphone/Localizable.xcstrings | 15 ++- Linphone/UI/Call/CallView.swift | 12 +- .../UI/Call/ViewModel/CallViewModel.swift | 51 ++++++- Linphone/UI/Main/Fragments/ToastView.swift | 29 +++- .../History/Fragments/StartCallFragment.swift | 124 ++++++++++++------ 6 files changed, 198 insertions(+), 49 deletions(-) diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index 674573e7c..81825b9a3 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -237,6 +237,22 @@ final class CoreContext: ObservableObject { } }) + self.mCoreSuscriptions.insert(self.mCore.publisher?.onTransferStateChanged?.postOnMainQueue { (cbValue: (_: Core, transfered: Call, callState: Call.State)) in + Log.info( + "[CoreContext] Transferred call \(cbValue.transfered.remoteAddress!.asStringUriOnly()) state changed \(cbValue.callState)" + ) + if cbValue.callState == Call.State.Connected { + ToastViewModel.shared.toastMessage = "Success_toast_call_transfer_successful" + ToastViewModel.shared.displayToast = true + } else if cbValue.callState == Call.State.OutgoingProgress { + ToastViewModel.shared.toastMessage = "Success_toast_call_transfer_in_progress" + ToastViewModel.shared.displayToast = true + } else if cbValue.callState == Call.State.End || cbValue.callState == Call.State.Error { + ToastViewModel.shared.toastMessage = "Failed_toast_call_transfer_failed" + ToastViewModel.shared.displayToast = true + } + }) + self.mIterateSuscription = Timer.publish(every: 0.02, on: .main, in: .common) .autoconnect() .receive(on: coreQueue) diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index ecffb1921..f42488b85 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -199,12 +199,21 @@ }, "Bluetooth" : { + }, + "Call has been successfully transferred" : { + }, "Call history" : { + }, + "Call is being transferred" : { + }, "Call list" : { + }, + "Call transfer failed!" : { + }, "Calls" : { @@ -232,9 +241,6 @@ }, "Contacts" : { - }, - "Content" : { - }, "Continue" : { @@ -558,9 +564,6 @@ }, "This contact will be deleted definitively." : { - }, - "Title" : { - }, "TLS" : { diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index 999f0c691..ac3d92628 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -741,10 +741,14 @@ struct CallView: View { HStack(spacing: 0) { VStack { Button { - withAnimation { - callViewModel.isTransferInsteadCall = true - MagicSearchSingleton.shared.searchForSuggestions() - isShowStartCallFragment.toggle() + if callViewModel.calls.count < 2 { + withAnimation { + callViewModel.isTransferInsteadCall = true + MagicSearchSingleton.shared.searchForSuggestions() + isShowStartCallFragment.toggle() + } + } else { + callViewModel.transferClicked() } } label: { Image("phone-transfer") diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift index cfd34c8eb..f6cf74e85 100644 --- a/Linphone/UI/Call/ViewModel/CallViewModel.swift +++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift @@ -43,7 +43,7 @@ class CallViewModel: ObservableObject { @Published var isMediaEncrypted: Bool = false @Published var isZrtpPq: Bool = false @Published var isRemoteDeviceTrusted: Bool = false - @Published var selectedCall: Call? = nil + @Published var selectedCall: Call? @Published var isTransferInsteadCall: Bool = false var calls: [Call] = [] @@ -408,4 +408,53 @@ class CallViewModel: ObservableObject { self.zrtpPopupDisplayed = true } } + + func transferClicked() { + coreContext.doOnCoreQueue { core in + var callToTransferTo = core.calls.last { call in + call.state == Call.State.Paused && call.callLog?.callId != self.currentCall?.callLog?.callId + } + + if (callToTransferTo == nil) { + Log.error( + "[CallViewModel] Couldn't find a call in Paused state to transfer current call to" + ) + } else { + if self.currentCall != nil && self.currentCall!.remoteAddress != nil && callToTransferTo!.remoteAddress != nil { + Log.info( + "[CallViewModel] Doing an attended transfer between currently displayed call \(self.currentCall!.remoteAddress!.asStringUriOnly()) " + + "and paused call \(callToTransferTo!.remoteAddress!.asStringUriOnly())" + ) + + do { + try callToTransferTo!.transferToAnother(dest: self.currentCall!) + Log.info("[CallViewModel] Attended transfer is successful") + } catch _ { + ToastViewModel.shared.toastMessage = "Failed_toast_call_transfer_failed" + ToastViewModel.shared.displayToast = true + + Log.error("[CallViewModel] Failed to make attended transfer!") + } + } + } + } + } + + func blindTransferCallTo(toAddress: Address) { + if self.currentCall != nil && self.currentCall!.remoteAddress != nil { + Log.info( + "[CallViewModel] Call \(self.currentCall!.remoteAddress!.asStringUriOnly()) is being blindly transferred to \(toAddress.asStringUriOnly())" + ) + + do { + try self.currentCall!.transferTo(referTo: toAddress) + Log.info("[CallViewModel] Blind call transfer is successful") + } catch _ { + ToastViewModel.shared.toastMessage = "Failed_toast_call_transfer_failed" + ToastViewModel.shared.displayToast = true + + Log.error("[CallViewModel] Failed to make blind call transfer!") + } + } + } } diff --git a/Linphone/UI/Main/Fragments/ToastView.swift b/Linphone/UI/Main/Fragments/ToastView.swift index 67e01a6b4..e42c834c3 100644 --- a/Linphone/UI/Main/Fragments/ToastView.swift +++ b/Linphone/UI/Main/Fragments/ToastView.swift @@ -27,7 +27,13 @@ struct ToastView: View { VStack { if toastViewModel.displayToast { HStack { - if toastViewModel.toastMessage.contains("Info_") { + if toastViewModel.toastMessage.contains("toast_call_transfer") { + Image("phone-transfer") + .resizable() + .renderingMode(.template) + .frame(width: 25, height: 25, alignment: .leading) + .foregroundStyle(toastViewModel.toastMessage.contains("Success") ? Color.greenSuccess500 : Color.redDanger500) + } else if toastViewModel.toastMessage.contains("Info_") { Image("trusted") .resizable() .frame(width: 25, height: 25, alignment: .leading) @@ -110,6 +116,27 @@ struct ToastView: View { .default_text_style(styleSize: 15) .padding(8) + case "Success_toast_call_transfer_successful": + Text("Call has been successfully transferred") + .multilineTextAlignment(.center) + .foregroundStyle(Color.greenSuccess500) + .default_text_style(styleSize: 15) + .padding(8) + + case "Success_toast_call_transfer_in_progress": + Text("Call is being transferred") + .multilineTextAlignment(.center) + .foregroundStyle(Color.greenSuccess500) + .default_text_style(styleSize: 15) + .padding(8) + + case "Failed_toast_call_transfer_failed": + Text("Call transfer failed!") + .multilineTextAlignment(.center) + .foregroundStyle(Color.redDanger500) + .default_text_style(styleSize: 15) + .padding(8) + default: Text("Error") .multilineTextAlignment(.center) diff --git a/Linphone/UI/Main/History/Fragments/StartCallFragment.swift b/Linphone/UI/Main/History/Fragments/StartCallFragment.swift index 2f90d0c43..7eea3b63f 100644 --- a/Linphone/UI/Main/History/Fragments/StartCallFragment.swift +++ b/Linphone/UI/Main/History/Fragments/StartCallFragment.swift @@ -177,26 +177,50 @@ struct StartCallFragment: View { } ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false), startCallFunc: { addr in - showingDialer = false - - DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) { - magicSearch.searchForContacts( - sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + if callViewModel.isTransferInsteadCall { + showingDialer = false - if callViewModel.isTransferInsteadCall == true { - callViewModel.isTransferInsteadCall = false + DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) { + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + + if callViewModel.isTransferInsteadCall == true { + callViewModel.isTransferInsteadCall = false + } + + resetCallView() } - resetCallView() - } - - startCallViewModel.searchField = "" - magicSearch.currentFilterSuggestions = "" - delayColorDismiss() - - withAnimation { - isShowStartCallFragment.toggle() - telecomManager.doCallWithCore(addr: addr, isVideo: false) + startCallViewModel.searchField = "" + magicSearch.currentFilterSuggestions = "" + delayColorDismiss() + + withAnimation { + isShowStartCallFragment.toggle() + callViewModel.blindTransferCallTo(toAddress: addr) + } + } else { + showingDialer = false + + DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) { + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + + if callViewModel.isTransferInsteadCall == true { + callViewModel.isTransferInsteadCall = false + } + + resetCallView() + } + + startCallViewModel.searchField = "" + magicSearch.currentFilterSuggestions = "" + delayColorDismiss() + + withAnimation { + isShowStartCallFragment.toggle() + telecomManager.doCallWithCore(addr: addr, isVideo: false) + } } }) .padding(.horizontal, 16) @@ -235,29 +259,55 @@ struct StartCallFragment: View { var suggestionsList: some View { ForEach(0..