From 3f5fd3303fccc0de49babc5d3143929191e66052 Mon Sep 17 00:00:00 2001 From: urmitchauhan <105660080+urmitchauhan@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:36:14 +0530 Subject: [PATCH] Release/3.14 (#82) (#83) KitchenSink example --- KitchenSink.xcodeproj/project.pbxproj | 8 ++--- .../Controllers/LoginViewController.swift | 4 +++ .../NewUI/ViewModels/CallViewModel.swift | 24 ++++++++++++- .../NewUI/ViewModels/LoginViewModel.swift | 14 +++++--- .../NewUI/ViewModels/SettingsViewModel.swift | 35 ++++++++++++++++++- .../NewUI/Views/MoreOptionsCallView.swift | 12 +++++++ KitchenSink/NewUI/Views/SettingsView.swift | 23 +++++++++++- .../NewUI/Webex/WebexAuthenticator.swift | 12 ++----- KitchenSink/NewUI/Webex/WebexCall.swift | 14 +++++++- KitchenSink/NewUI/Webex/WebexPhone.swift | 16 ++++++++- Podfile | 8 ++--- 11 files changed, 143 insertions(+), 27 deletions(-) diff --git a/KitchenSink.xcodeproj/project.pbxproj b/KitchenSink.xcodeproj/project.pbxproj index c053731..be1516d 100644 --- a/KitchenSink.xcodeproj/project.pbxproj +++ b/KitchenSink.xcodeproj/project.pbxproj @@ -1834,7 +1834,7 @@ CODE_SIGN_ENTITLEMENTS = KitchenSink/KitchenSink.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3130; + CURRENT_PROJECT_VERSION = 3140; DEVELOPMENT_TEAM = 9X38D433RE; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -1857,7 +1857,7 @@ "@executable_path/Frameworks", executable_path/Frameworks, ); - MARKETING_VERSION = 3.13; + MARKETING_VERSION = 3.14; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.webex.sdk.KitchenSinkv3.0; @@ -1894,7 +1894,7 @@ CODE_SIGN_ENTITLEMENTS = KitchenSink/KitchenSink.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3130; + CURRENT_PROJECT_VERSION = 3140; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 9X38D433RE; ENABLE_BITCODE = NO; @@ -1913,7 +1913,7 @@ "@executable_path/Frameworks", executable_path/Frameworks, ); - MARKETING_VERSION = 3.13; + MARKETING_VERSION = 3.14; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.webex.sdk.KitchenSinkv3.0; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/KitchenSink/Controllers/LoginViewController.swift b/KitchenSink/Controllers/LoginViewController.swift index e29428f..df9f3c0 100644 --- a/KitchenSink/Controllers/LoginViewController.swift +++ b/KitchenSink/Controllers/LoginViewController.swift @@ -281,6 +281,10 @@ class LoginViewController: UIViewController { self?.loginButton.isEnabled = true print("Login failed!") UserDefaults.standard.removeObject(forKey: Constants.emailKey) + + let alert = UIAlertController(title: "Error", message: result.rawValue, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self?.present(alert, animated: true, completion: nil) return } UserDefaults.standard.setValue(Constants.loginTypeValue.email.rawValue, forKey: Constants.loginTypeKey) diff --git a/KitchenSink/NewUI/ViewModels/CallViewModel.swift b/KitchenSink/NewUI/ViewModels/CallViewModel.swift index 671773f..c99143c 100644 --- a/KitchenSink/NewUI/ViewModels/CallViewModel.swift +++ b/KitchenSink/NewUI/ViewModels/CallViewModel.swift @@ -143,6 +143,7 @@ class CallViewModel: ObservableObject @Published var showDTMFControl = false @Published var placeholderText1 = "" @Published var placeholderText2 = "" + @Published var speechEnhancement = false let renderModes: [Call.VideoRenderMode] = [.fit, .cropFill, .stretchFill] let flashModes: [Call.FlashMode] = [.on, .off, .auto] let torchModes: [Call.TorchMode] = [.on, .off, .auto] @@ -835,6 +836,7 @@ class CallViewModel: ObservableObject self?.canControlWXA = call.canControlWXA self?.isClosedCaptionAllowed = call.isClosedCaptionAllowed self?.isClosedCaptionEnabled = call.isClosedCaptionEnabled + self?.speechEnhancement = call.isSpeechEnhancementEnabled } self.updateNameLabels(connected: call.isConnected) } @@ -888,7 +890,27 @@ class CallViewModel: ObservableObject self?.currentCall?.receivingScreenShare = isOn } } - + + // Handles Speech Enhancement for the call. + func handleSpeechEnhancement(isOn: Bool) { + if isOn == self.speechEnhancement { + return + } + self.currentCall?.enableSpeechEnhancement(shouldEnable: isOn, completionHandler: { result in + switch result + { + case .success(): + DispatchQueue.main.async { [weak self] in + self?.speechEnhancement.toggle() + } + case .failure(let err): + DispatchQueue.main.async { [weak self] in + self?.showError("Speech Enhancement Error ", err.localizedDescription) + } + } + }) + } + // Handles EndCall Action. func handleEndCall() { diff --git a/KitchenSink/NewUI/ViewModels/LoginViewModel.swift b/KitchenSink/NewUI/ViewModels/LoginViewModel.swift index ac6754f..dd3ac64 100644 --- a/KitchenSink/NewUI/ViewModels/LoginViewModel.swift +++ b/KitchenSink/NewUI/ViewModels/LoginViewModel.swift @@ -70,17 +70,21 @@ class LoginViewModel: ObservableObject { webex = Webex(authenticator: authenticator) loadingIndicator(show: true) guard let redirectUri = self.getRedirectUri() else { return } - self.webexAuthenticator.getAuthorizationUrl(authenticator: authenticator, completion: { url in - guard let url = url else { + self.webexAuthenticator.getAuthorizationUrl(authenticator: authenticator) { result, url in + if result == .success, let url = url { + self.link = url + self.redirectUri = redirectUri + self.showWebView = true + } + else { self.showErrorAlert = true - self.alertErrorMessage = "Invalid email" + self.alertErrorMessage = result.rawValue self.loadingIndicator(show: false) - return } self.link = url self.redirectUri = redirectUri self.showWebView = true - }) + } } /// Logs in using the authentication code. diff --git a/KitchenSink/NewUI/ViewModels/SettingsViewModel.swift b/KitchenSink/NewUI/ViewModels/SettingsViewModel.swift index 213a726..5ccafaa 100644 --- a/KitchenSink/NewUI/ViewModels/SettingsViewModel.swift +++ b/KitchenSink/NewUI/ViewModels/SettingsViewModel.swift @@ -17,6 +17,10 @@ class SettingsViewModel: ObservableObject { @Published var enableBackgroundConnection = false @Published var isAuxiliaryMode = false @Published var enable1080pVideo = false + @Published var useLegacyNoiseRemoval = false + @Published var enableSpeechEnhancement = false + @Published var showError: Bool = false + @Published var error: String = "" @Published var videoStreamModeLabel = "" var webexAuthenticator = WebexAuthenticator() @@ -31,6 +35,14 @@ class SettingsViewModel: ObservableObject { self.mailVM = mailVM } + /// Asynchronously displays an error message on the main queue. + func showError(error: Error) { + DispatchQueue.main.async { + self.showError = true + self.error = error.localizedDescription + } + } + /// Fetches and updates the version of the Webex SDK and the build version of the application. func updateVersion() { let bundleVersion = Bundle.main.infoDictionary!["CFBundleVersion"] as! String @@ -78,8 +90,9 @@ class SettingsViewModel: ObservableObject { isStartCallWithVideoOn = UserDefaults.standard.bool(forKey: "hasVideo") isAuxiliaryMode = UserDefaults.standard.bool(forKey: "compositeMode") enable1080pVideo = UserDefaults.standard.bool(forKey: "VideoRes1080p") - enable1080pVideo = UserDefaults.standard.bool(forKey: "VideoRes1080p") enableBackgroundConnection = UserDefaults.standard.bool(forKey: "backgroundConnection") + useLegacyNoiseRemoval = UserDefaults.standard.bool(forKey: "legacyNoiseRemoval") + enableSpeechEnhancement = webexPhone.isSpeechEnhancementEnabled } func updateStartCallWithVideoOn() { @@ -116,4 +129,24 @@ class SettingsViewModel: ObservableObject { webexPhone.enableBackgroundConnection = false } } + + func updateUseLegacyNoiseRemoval() { + useLegacyNoiseRemoval.toggle() + webexPhone.useLegacyReceiverNoiseRemoval(useLegacy: useLegacyNoiseRemoval) + UserDefaults.standard.setValue(useLegacyNoiseRemoval, forKey: "legacyNoiseRemoval") + } + + func updateSpeechEnhancementEnabled() { + enableSpeechEnhancement.toggle() + webexPhone.enableSpeechEnhancement(shouldEnable: enableSpeechEnhancement, completionHandler: { result in + switch result { + case .success(): + self.enableSpeechEnhancement.toggle() + case .failure(let err): + DispatchQueue.main.async { + self.showError(error: err) + } + } + }) + } } diff --git a/KitchenSink/NewUI/Views/MoreOptionsCallView.swift b/KitchenSink/NewUI/Views/MoreOptionsCallView.swift index a04403b..e4ab6c2 100644 --- a/KitchenSink/NewUI/Views/MoreOptionsCallView.swift +++ b/KitchenSink/NewUI/Views/MoreOptionsCallView.swift @@ -7,6 +7,7 @@ struct MoreOptionsCallView: View { @State private var receivingVideo = false @State private var receivingAudio = false @State private var receivingScreenShare = false + @State private var speechEnhancement = false //@State private var isVirtualBGListPresent = false @State private var externalCamera = false var isVirtualBGListPresent: Bool { @@ -69,6 +70,17 @@ struct MoreOptionsCallView: View { } } .accessibilityIdentifier("receivingScreenShareToggle") + + Toggle("Speech Enhancement ", isOn: $speechEnhancement) + .onChange(of: speechEnhancement) { newValue in + callingVM.handleSpeechEnhancement(isOn: newValue) + } + .onReceive(callingVM.$speechEnhancement) { newValue in + if newValue != speechEnhancement { + speechEnhancement = newValue + } + } + .accessibilityIdentifier("speechEnhancementToggle") } if callingVM.isCUCMOrWxcCall { diff --git a/KitchenSink/NewUI/Views/SettingsView.swift b/KitchenSink/NewUI/Views/SettingsView.swift index e9ce02d..f6b64f9 100644 --- a/KitchenSink/NewUI/Views/SettingsView.swift +++ b/KitchenSink/NewUI/Views/SettingsView.swift @@ -16,6 +16,7 @@ struct SettingsView: View { @State private var alertTitle = "" @State private var isPhoneServicesOn = false @State private var showSetupView = false + @State private var isSpeechEnhancementEnabled: Bool = true @Environment(\.dismiss) var dismiss @@ -72,7 +73,21 @@ struct SettingsView: View { }.onTapGesture { model.updateStartCallWithVideoOn() } - + + HStack { + Toggle("Use Legacy Noise Removal", isOn: $model.useLegacyNoiseRemoval) + .accessibilityIdentifier("isSpeechEnhancementEnabled") + }.onTapGesture { + model.updateUseLegacyNoiseRemoval() + } + + HStack { + Toggle("Enable Speech Enhancement", isOn: $model.enableSpeechEnhancement) + .accessibilityIdentifier("isSpeechEnhancementEnabled") + }.onTapGesture { + model.updateSpeechEnhancementEnabled() + } + HStack { Toggle("Auxiliary Mode", isOn: $model.isAuxiliaryMode) .accessibilityIdentifier("auxiliaryMode") @@ -156,6 +171,12 @@ struct SettingsView: View { .foregroundColor(.secondary) } }// nav + .alert("Error", isPresented: $model.showError) { + Button("Ok") { } + .accessibilityIdentifier("errorOkButton") + } message: { + Text(model.error) + } .alert("Could Not Send Email", isPresented: $mailSentFailAlert) { Button("Ok") {} } message: { diff --git a/KitchenSink/NewUI/Webex/WebexAuthenticator.swift b/KitchenSink/NewUI/Webex/WebexAuthenticator.swift index 0dad93b..504bb45 100644 --- a/KitchenSink/NewUI/Webex/WebexAuthenticator.swift +++ b/KitchenSink/NewUI/Webex/WebexAuthenticator.swift @@ -3,7 +3,7 @@ import WebexSDK protocol OAuthAuthenticationProtocol: AnyObject { func getOAuthAuthenticator(email: String, isFedRAMP: Bool) -> OAuthAuthenticator? - func getAuthorizationUrl(authenticator: OAuthAuthenticator, completion: @escaping ((URL?) -> Void)) + func getAuthorizationUrl(authenticator: OAuthAuthenticator, completion: @escaping ((OAuthResult, URL?) -> Void)) func loginWithAuthCode(code: String, completion: @escaping (Bool) -> Void) } @@ -65,14 +65,8 @@ extension WebexAuthenticator : OAuthAuthenticationProtocol { } /// Retrieves the authorization URL. - func getAuthorizationUrl(authenticator: OAuthAuthenticator, completion: @escaping ((URL?) -> Void)) { - authenticator.getAuthorizationUrl(completionHandler: { result, url in - if result == .success { - completion(url) - } else { - completion(nil) - } - }) + func getAuthorizationUrl(authenticator: OAuthAuthenticator, completion: @escaping ((OAuthResult, URL?) -> Void)) { + authenticator.getAuthorizationUrl(completionHandler: completion) } /// Login the user using an authorization code. diff --git a/KitchenSink/NewUI/Webex/WebexCall.swift b/KitchenSink/NewUI/Webex/WebexCall.swift index 4720482..b3307d1 100644 --- a/KitchenSink/NewUI/Webex/WebexCall.swift +++ b/KitchenSink/NewUI/Webex/WebexCall.swift @@ -22,6 +22,7 @@ public protocol CallProtocol: AnyObject { var isWXAEnabled: Bool {get set} var isClosedCaptionEnabled: Bool {get} var isClosedCaptionAllowed: Bool {get} + var isSpeechEnhancementEnabled: Bool {get} var videoRenderViews: (local: MediaRenderView?, remote: MediaRenderView?) {get set} var screenShareView: MediaRenderView? {get set} var mediaStream: MediaStream? {get set} @@ -119,6 +120,7 @@ public protocol CallProtocol: AnyObject { func getClosedCaptions() -> [CaptionItem] func enableWXA(isEnabled: Bool, callback:@escaping ((Bool)->Void)) -> Void func send(dtmfCode: String, completionHandler: ((Error?) -> Void)?) + func enableSpeechEnhancement(shouldEnable: Bool, completionHandler: @escaping (Result) -> Void) } @available(iOS 16.0, *) @@ -272,7 +274,13 @@ class CallKS: CallProtocol var cameraDuration: WebexSDK.Call.CameraExposureDuration? { return call?.exposureDuration } - + + var isSpeechEnhancementEnabled: Bool { + get { + return call?.isReceiverSpeechEnhancementEnabled ?? false + } + } + private var call: Call? init(call: WebexSDK.Call) { @@ -678,6 +686,10 @@ class CallKS: CallProtocol public func send(dtmfCode: String, completionHandler: ((Error?) -> Void)?) { call?.send(dtmf: dtmfCode, completionHandler: completionHandler) } + + public func enableSpeechEnhancement(shouldEnable: Bool, completionHandler: @escaping (Result) -> Void) { + call?.enableReceiverSpeechEnhancement(shouldEnable: shouldEnable, completionHandler: completionHandler) + } } extension Call.BreakoutSession: Hashable { diff --git a/KitchenSink/NewUI/Webex/WebexPhone.swift b/KitchenSink/NewUI/Webex/WebexPhone.swift index 70c250a..31b1de0 100644 --- a/KitchenSink/NewUI/Webex/WebexPhone.swift +++ b/KitchenSink/NewUI/Webex/WebexPhone.swift @@ -11,7 +11,8 @@ protocol PhoneProtocol: AnyObject var videoMaxRxBandwidth: UInt32 { get set } var videoMaxTxBandwidth: UInt32 { get set } var defaultFacingMode: FacingModeKS { get set } - + var isSpeechEnhancementEnabled: Bool {get} + func setPushTokens(bundleId: String, deviceId: String, deviceToken: String, voipToken: String, appId: String?) func dial(joinAddress: String, isPhoneNumber: Bool, isMoveMeeting: Bool, isModerator: Bool, pin: String?, captchaId: String, captchaVerifyCode: String, selfVideoView: MediaRenderViewKS, remoteVideoViewRepresentable: RemoteVideoViewRepresentable, screenShareView: MediaRenderViewKS, completionHandler: @escaping (Swift.Result) -> Void) @@ -25,6 +26,8 @@ protocol PhoneProtocol: AnyObject func refreshMeetingCaptcha(completionHandler: @escaping (Result) -> Void) func updateSystemPreferredCamera(camera: Camera, completionHandler: @escaping (Result) -> Void) func getListOfCameras() -> [Camera] + func useLegacyReceiverNoiseRemoval(useLegacy: Bool) + func enableSpeechEnhancement(shouldEnable: Bool, completionHandler: @escaping (Result) -> Void) } @available(iOS 16.0, *) @@ -117,6 +120,10 @@ class WebexPhone: PhoneProtocol { } } + var isSpeechEnhancementEnabled: Bool { + return webex.phone.isReceiverSpeechEnhancementEnabled + } + // Sets the push tokens for WxC calling push notifications func setPushTokens(bundleId: String, deviceId: String, deviceToken: String, voipToken: String, appId: String? = nil) { webex.phone.setPushTokens(bundleId: bundleId, deviceId: deviceId, deviceToken: deviceToken, voipToken: voipToken, appId: nil) @@ -215,6 +222,13 @@ class WebexPhone: PhoneProtocol { return webex.phone.getListOfCameras() } + func useLegacyReceiverNoiseRemoval(useLegacy: Bool) { + webex.phone.useLegacyReceiverNoiseRemoval(useLegacy: useLegacy) + } + + func enableSpeechEnhancement(shouldEnable: Bool, completionHandler: @escaping (Result) -> Void) { + webex.phone.enableReceiverSpeechEnhancement(shouldEnable: shouldEnable, completionHandler: completionHandler) + } } diff --git a/Podfile b/Podfile index dfef982..de1a49f 100644 --- a/Podfile +++ b/Podfile @@ -7,9 +7,9 @@ target 'KitchenSink' do use_frameworks! # Pods for KitchenSink - pod 'WebexSDK','~> 3.13.0' - # pod 'WebexSDK/Meeting','~> 3.13.0' # Uncomment this line and comment the above line for Meeting-only SDK - # pod 'WebexSDK/Wxc','~> 3.13.0' # Uncomment this line and comment the above line for Calling-only SDK + pod 'WebexSDK','~> 3.14.0' + # pod 'WebexSDK/Meeting','~> 3.14.0' # Uncomment this line and comment the above line for Meeting-only SDK + # pod 'WebexSDK/Wxc','~> 3.14.0' # Uncomment this line and comment the above line for Calling-only SDK target 'KitchenSinkUITests' do @@ -23,7 +23,7 @@ target 'KitchenSinkBroadcastExtension' do use_frameworks! # Pods for KitchenSinkBroadcastExtension - pod 'WebexBroadcastExtensionKit','~> 3.13.0' + pod 'WebexBroadcastExtensionKit','~> 3.14.0' end