diff --git a/.gitignore b/.gitignore index 99358f2..1bf8bc9 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ ios/swift/*/*.xcodeproj/project.xcworkspace/xcuserdata/ ios/swift/*/*.xcodeproj/xcuserdata/ ios/swift/*/*.xcworkspace/xcuserdata/ ios/swift/*/build +ios/swift/*/*.xcworkspace diff --git a/ios/README.md b/ios/README.md new file mode 100644 index 0000000..d21db59 --- /dev/null +++ b/ios/README.md @@ -0,0 +1,12 @@ +IOS tutorials +==================== + +Tutorials are written in swift, but the same features can be achieved in objective-C. + +Tutorials are numbered 0 to 6, and we recommend you to read them in that order as features from previous tutorials are sometimes used in the next ones, such as account login. + +Each tutorial is a full project, so you can generate your Xcode workspace with Cocoapods, build it and deploy it on a real device or an emulator. + +Code is being kept as short and simple as possible, and comments explain how and why things are being done. + +Full swift API is available [here](http://linphone.org/snapshots/docs/liblinphone/latest/swift). diff --git a/ios/swift/0-Helloworld/Helloworld.xcodeproj/project.pbxproj b/ios/swift/0-Helloworld/Helloworld.xcodeproj/project.pbxproj index e48ebdb..d6334a8 100644 --- a/ios/swift/0-Helloworld/Helloworld.xcodeproj/project.pbxproj +++ b/ios/swift/0-Helloworld/Helloworld.xcodeproj/project.pbxproj @@ -13,7 +13,7 @@ 663D8CE526E8E35500EE487F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 663D8CE426E8E35500EE487F /* Assets.xcassets */; }; 663D8CE826E8E35500EE487F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 663D8CE726E8E35500EE487F /* Preview Assets.xcassets */; }; 663D8CEB26E8E35500EE487F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 663D8CE926E8E35500EE487F /* LaunchScreen.storyboard */; }; - 663D8CF326E8E73700EE487F /* HelloworldTutorial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663D8CF226E8E73700EE487F /* HelloworldTutorial.swift */; }; + 667C3B4826F1E7B2004D4F14 /* HelloworldTutorial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667C3B4726F1E7B2004D4F14 /* HelloworldTutorial.swift */; }; AE5DCE54FB07B00CDB39CF8C /* Pods_Helloworld.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 658B93FC56A6311767BE2F24 /* Pods_Helloworld.framework */; }; /* End PBXBuildFile section */ @@ -28,7 +28,7 @@ 663D8CE726E8E35500EE487F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 663D8CEA26E8E35500EE487F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 663D8CEC26E8E35500EE487F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 663D8CF226E8E73700EE487F /* HelloworldTutorial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HelloworldTutorial.swift; path = ../../../../Downloads/HelloworldTutorial.swift; sourceTree = ""; }; + 667C3B4726F1E7B2004D4F14 /* HelloworldTutorial.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelloworldTutorial.swift; sourceTree = ""; }; B14B29CDA5B52FB0218CB545 /* Pods-Helloworld.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Helloworld.release.xcconfig"; path = "Target Support Files/Pods-Helloworld/Pods-Helloworld.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -71,8 +71,8 @@ 663D8CE426E8E35500EE487F /* Assets.xcassets */, 663D8CE926E8E35500EE487F /* LaunchScreen.storyboard */, 663D8CEC26E8E35500EE487F /* Info.plist */, + 667C3B4726F1E7B2004D4F14 /* HelloworldTutorial.swift */, 663D8CE626E8E35500EE487F /* Preview Content */, - 663D8CF226E8E73700EE487F /* HelloworldTutorial.swift */, ); path = Helloworld; sourceTree = ""; @@ -219,7 +219,7 @@ 663D8CDF26E8E35400EE487F /* AppDelegate.swift in Sources */, 663D8CE126E8E35400EE487F /* SceneDelegate.swift in Sources */, 663D8CE326E8E35400EE487F /* ContentView.swift in Sources */, - 663D8CF326E8E73700EE487F /* HelloworldTutorial.swift in Sources */, + 667C3B4826F1E7B2004D4F14 /* HelloworldTutorial.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/swift/0-Helloworld/Helloworld/HelloworldTutorial.swift b/ios/swift/0-Helloworld/Helloworld/HelloworldTutorial.swift new file mode 100644 index 0000000..f4bee58 --- /dev/null +++ b/ios/swift/0-Helloworld/Helloworld/HelloworldTutorial.swift @@ -0,0 +1,58 @@ +// +// HellowolrdTutorial.swift +// HellowolrdTutorial +// +// Created by QuentinArguillere on 08/09/2021. +// Copyright © 2021 BelledonneCommunications. All rights reserved. +// + +// Check the Podfile to see how to import the LibLinphone SDK !!! +import linphonesw + +class HelloworldTutorialContext : ObservableObject +{ + var mCore: Core! + @Published var coreVersion: String = Core.getVersion + + /*------------ Login tutorial related variables -------*/ + var mRegistrationDelegate : CoreDelegate! + @Published var username : String = "user" + @Published var passwd : String = "pwd" + @Published var domain : String = "sip.example.org" + @Published var loggedIn: Bool = false + @Published var transportType : String = "TLS" + + init() + { + + // Some configuration can be done before the Core is created, for example enable debug logs. + LoggingService.Instance.logLevel = LogLevel.Debug + + // Core is the main object of the SDK. You can't do much without it. + // To create a Core, we need the instance of the Factory. + let factory = Factory.Instance + + // Your Core can use up to 2 configuration files, but that isn't mandatory. + try! mCore = factory.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) + try! mCore.start() + + // Create a Core listener to listen for the callback we need + // In this case, we want to know about the account registration status + mRegistrationDelegate = CoreDelegateStub(onAccountRegistrationStateChanged: { (core: Core, account: Account, state: RegistrationState, message: String) in + + // If account has been configured correctly, we will go through Progress and Ok states + // Otherwise, we will be Failed. + NSLog("New registration state is \(state) for user id \( String(describing: account.params?.identityAddress?.asString()))\n") + if (state == .Ok) { + self.loggedIn = true + } else if (state == .Cleared) { + self.loggedIn = false + } + }) + mCore.addDelegate(delegate: mRegistrationDelegate) + coreVersion = Core.getVersion + // Now we can start using the Core object + } + + +} diff --git a/ios/swift/1-LoginTutorial/LoginTutorial/ContentView.swift b/ios/swift/1-LoginTutorial/LoginTutorial/ContentView.swift index 8985de4..a35acae 100644 --- a/ios/swift/1-LoginTutorial/LoginTutorial/ContentView.swift +++ b/ios/swift/1-LoginTutorial/LoginTutorial/ContentView.swift @@ -9,27 +9,27 @@ import SwiftUI struct ContentView: View { - + @ObservedObject var tutorialContext : LoginTutorialContext - - var body: some View { - - VStack { - Group { - HStack { - Text("Username:") - .font(.title) + + var body: some View { + + VStack { + Group { + HStack { + Text("Username:") + .font(.title) TextField("", text : $tutorialContext.username) - .textFieldStyle(RoundedBorderTextFieldStyle()) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } - HStack { - Text("Password:") - .font(.title) - TextField("", text : $tutorialContext.passwd) - .textFieldStyle(RoundedBorderTextFieldStyle()) + } + HStack { + Text("Password:") + .font(.title) + TextField("", text : $tutorialContext.passwd) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } + } HStack { Text("Domain:") .font(.title) @@ -38,50 +38,50 @@ struct ContentView: View { .disabled(tutorialContext.loggedIn) } Picker(selection: $tutorialContext.transportType, label: Text("Transport:")) { - Text("TLS").tag("TLS") - Text("TCP").tag("TCP") - Text("UDP").tag("UDP") + Text("TLS").tag("TLS") + Text("TCP").tag("TCP") + Text("UDP").tag("UDP") }.pickerStyle(SegmentedPickerStyle()).padding() - VStack { - HStack { - Button(action: { - if (self.tutorialContext.loggedIn) - { - self.tutorialContext.unregister() + VStack { + HStack { + Button(action: { + if (self.tutorialContext.loggedIn) + { + self.tutorialContext.unregister() self.tutorialContext.delete() - } else { - self.tutorialContext.login() - } - }) - { - Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") - .font(.largeTitle) - .foregroundColor(Color.white) + } else { + self.tutorialContext.login() + } + }) + { + Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") + .font(.largeTitle) + .foregroundColor(Color.white) .frame(width: 220.0, height: 90) - .background(Color.gray) - } - - } - HStack { - Text("Login State : ") - .font(.footnote) - Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") - .font(.footnote) - .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) - }.padding(.top, 10.0) - } - } - Group { - Spacer() - Text("Core Version is \(tutorialContext.coreVersion)") - } - } - .padding() - } + .background(Color.gray) + } + + } + HStack { + Text("Login State : ") + .font(.footnote) + Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") + .font(.footnote) + .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) + }.padding(.top, 10.0) + } + } + Group { + Spacer() + Text("Core Version is \(tutorialContext.coreVersion)") + } + } + .padding() + } } struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView(tutorialContext: LoginTutorialContext()) - } + static var previews: some View { + ContentView(tutorialContext: LoginTutorialContext()) + } } diff --git a/ios/swift/1-LoginTutorial/LoginTutorial/LoginTutorial.swift b/ios/swift/1-LoginTutorial/LoginTutorial/LoginTutorial.swift index 8590751..878f849 100644 --- a/ios/swift/1-LoginTutorial/LoginTutorial/LoginTutorial.swift +++ b/ios/swift/1-LoginTutorial/LoginTutorial/LoginTutorial.swift @@ -10,24 +10,24 @@ import linphonesw class LoginTutorialContext : ObservableObject { - var mCore: Core! - @Published var coreVersion: String = Core.getVersion - - /*------------ Login tutorial related variables -------*/ + var mCore: Core! + @Published var coreVersion: String = Core.getVersion + + /*------------ Login tutorial related variables -------*/ var mRegistrationDelegate : CoreDelegate! - @Published var username : String = "user" - @Published var passwd : String = "pwd" + @Published var username : String = "user" + @Published var passwd : String = "pwd" @Published var domain : String = "sip.example.org" - @Published var loggedIn: Bool = false + @Published var loggedIn: Bool = false @Published var transportType : String = "TLS" - init() - { + init() + { LoggingService.Instance.logLevel = LogLevel.Debug - try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) - try? mCore.start() + try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) + try? mCore.start() // Create a Core listener to listen for the callback we need // In this case, we want to know about the account registration status @@ -43,8 +43,8 @@ class LoginTutorialContext : ObservableObject } }) mCore.addDelegate(delegate: mRegistrationDelegate) - } - + } + func login() { do { @@ -64,39 +64,39 @@ class LoginTutorialContext : ObservableObject // ha1 is set to null as we are using the clear text password. Upon first register, the hash will be computed automatically. // The realm will be determined automatically from the first register, as well as the algorithm let authInfo = try Factory.Instance.createAuthInfo(username: username, userid: "", passwd: passwd, ha1: "", realm: "", domain: domain) - + // Account object replaces deprecated ProxyConfig object // Account object is configured through an AccountParams object that we can obtain from the Core let accountParams = try mCore.createAccountParams() - + // A SIP account is identified by an identity address that we can construct from the username and domain let identity = try Factory.Instance.createAddress(addr: String("sip:" + username + "@" + domain)) try! accountParams.setIdentityaddress(newValue: identity) - + // We also need to configure where the proxy server is located let address = try Factory.Instance.createAddress(addr: String("sip:" + domain)) - + // We use the Address object to easily set the transport protocol try address.setTransport(newValue: transport) try accountParams.setServeraddress(newValue: address) // And we ensure the account will start the registration process accountParams.registerEnabled = true - + // Now that our AccountParams is configured, we can create the Account object let account = try mCore.createAccount(params: accountParams) - + // Now let's add our objects to the Core mCore.addAuthInfo(info: authInfo) try mCore.addAccount(account: account) - + // Also set the newly added account as default mCore.defaultAccount = account - + } catch { NSLog(error.localizedDescription) } } - func unregister() - { + func unregister() + { // Here we will disable the registration of our Account if let account = mCore.defaultAccount { @@ -110,19 +110,19 @@ class LoginTutorialContext : ObservableObject // And apply them account.params = clonedParams } - } + } func delete() { // To completely remove an Account if let account = mCore.defaultAccount { mCore.removeAccount(account: account) - + // To remove all accounts use mCore.clearAccounts() - + // Same for auth info mCore.clearAllAuthInfo() } } - - + + } diff --git a/ios/swift/2-IncomingCall/IncomingCall/ContentView.swift b/ios/swift/2-IncomingCall/IncomingCall/ContentView.swift index c2571c1..817ce6b 100644 --- a/ios/swift/2-IncomingCall/IncomingCall/ContentView.swift +++ b/ios/swift/2-IncomingCall/IncomingCall/ContentView.swift @@ -9,7 +9,7 @@ import SwiftUI struct ContentView: View { - + @ObservedObject var tutorialContext : IncomingCallTutorialContext func callStateString() -> String { @@ -22,24 +22,24 @@ struct ContentView: View { } } - var body: some View { - - VStack { - Group { - HStack { - Text("Username:") - .font(.title) + var body: some View { + + VStack { + Group { + HStack { + Text("Username:") + .font(.title) TextField("", text : $tutorialContext.username) - .textFieldStyle(RoundedBorderTextFieldStyle()) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } - HStack { - Text("Password:") - .font(.title) - TextField("", text : $tutorialContext.passwd) - .textFieldStyle(RoundedBorderTextFieldStyle()) + } + HStack { + Text("Password:") + .font(.title) + TextField("", text : $tutorialContext.passwd) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } + } HStack { Text("Domain:") .font(.title) @@ -48,37 +48,37 @@ struct ContentView: View { .disabled(tutorialContext.loggedIn) } Picker(selection: $tutorialContext.transportType, label: Text("Transport:")) { - Text("TLS").tag("TLS") - Text("TCP").tag("TCP") - Text("UDP").tag("UDP") + Text("TLS").tag("TLS") + Text("TCP").tag("TCP") + Text("UDP").tag("UDP") }.pickerStyle(SegmentedPickerStyle()).padding() - VStack { - HStack { - Button(action: { - if (self.tutorialContext.loggedIn) - { - self.tutorialContext.unregister() + VStack { + HStack { + Button(action: { + if (self.tutorialContext.loggedIn) + { + self.tutorialContext.unregister() self.tutorialContext.delete() - } else { - self.tutorialContext.login() - } - }) - { - Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") - .font(.largeTitle) - .foregroundColor(Color.white) + } else { + self.tutorialContext.login() + } + }) + { + Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") + .font(.largeTitle) + .foregroundColor(Color.white) .frame(width: 220.0, height: 90) - .background(Color.gray) - } - } - HStack { - Text("Login State : ") - .font(.footnote) - Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") - .font(.footnote) - .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) - }.padding(.top, 10.0) - } + .background(Color.gray) + } + } + HStack { + Text("Login State : ") + .font(.footnote) + Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") + .font(.footnote) + .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) + }.padding(.top, 10.0) + } VStack { HStack { Button(action: { @@ -90,10 +90,10 @@ struct ContentView: View { }) { Text( (tutorialContext.isCallRunning) ? "Terminate" : "Accept") - .font(.largeTitle) - .foregroundColor(Color.white) - .frame(width: 180.0, height: 42.0) - .background(Color.gray) + .font(.largeTitle) + .foregroundColor(Color.white) + .frame(width: 180.0, height: 42.0) + .background(Color.gray) } .disabled(!tutorialContext.isCallIncoming && !tutorialContext.isCallRunning) HStack { @@ -132,18 +132,18 @@ struct ContentView: View { .disabled(!tutorialContext.isCallRunning) }.padding(.top, 10) }.padding(.top, 30) - } - Group { - Spacer() - Text("Core Version is \(tutorialContext.coreVersion)") - } - } - .padding() - } + } + Group { + Spacer() + Text("Core Version is \(tutorialContext.coreVersion)") + } + } + .padding() + } } struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView(tutorialContext: IncomingCallTutorialContext()) - } + static var previews: some View { + ContentView(tutorialContext: IncomingCallTutorialContext()) + } } diff --git a/ios/swift/2-IncomingCall/IncomingCall/IncomingCall.swift b/ios/swift/2-IncomingCall/IncomingCall/IncomingCall.swift index 8d0ea5d..1767c00 100644 --- a/ios/swift/2-IncomingCall/IncomingCall/IncomingCall.swift +++ b/ios/swift/2-IncomingCall/IncomingCall/IncomingCall.swift @@ -10,15 +10,15 @@ import linphonesw class IncomingCallTutorialContext : ObservableObject { - var mCore: Core! - @Published var coreVersion: String = Core.getVersion - - var mAccount: Account? + var mCore: Core! + @Published var coreVersion: String = Core.getVersion + + var mAccount: Account? var mCoreDelegate : CoreDelegate! - @Published var username : String = "user" - @Published var passwd : String = "pwd" + @Published var username : String = "user" + @Published var passwd : String = "pwd" @Published var domain : String = "sip.example.org" - @Published var loggedIn: Bool = false + @Published var loggedIn: Bool = false @Published var transportType : String = "TLS" // Incoming call related variables @@ -29,12 +29,12 @@ class IncomingCallTutorialContext : ObservableObject @Published var isSpeakerEnabled : Bool = false @Published var isMicrophoneEnabled : Bool = false - init() - { + init() + { LoggingService.Instance.logLevel = LogLevel.Debug - try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) - try? mCore.start() + try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) + try? mCore.start() mCoreDelegate = CoreDelegateStub( onCallStateChanged: { (core: Core, call: Call, state: Call.State, message: String) in self.callMsg = message @@ -64,8 +64,8 @@ class IncomingCallTutorialContext : ObservableObject } }) mCore.addDelegate(delegate: mCoreDelegate) - } - + } + func login() { do { @@ -86,19 +86,19 @@ class IncomingCallTutorialContext : ObservableObject mCore.addAuthInfo(info: authInfo) try mCore.addAccount(account: mAccount!) mCore.defaultAccount = mAccount - + } catch { NSLog(error.localizedDescription) } } - func unregister() - { + func unregister() + { if let account = mCore.defaultAccount { let params = account.params let clonedParams = params?.clone() clonedParams?.registerEnabled = false account.params = clonedParams } - } + } func delete() { if let account = mCore.defaultAccount { mCore.removeAccount(account: account) @@ -106,7 +106,7 @@ class IncomingCallTutorialContext : ObservableObject mCore.clearAllAuthInfo() } } - + func terminateCall() { do { // Terminates the call, whether it is ringing or running @@ -130,12 +130,12 @@ class IncomingCallTutorialContext : ObservableObject mCore.micEnabled = !mCore.micEnabled isMicrophoneEnabled = !isMicrophoneEnabled } - + func toggleSpeaker() { // Get the currently used audio device let currentAudioDevice = mCore.currentCall?.outputAudioDevice let speakerEnabled = currentAudioDevice?.type == AudioDeviceType.Speaker - + let test = currentAudioDevice?.deviceName // We can get a list of all available audio devices using // Note that on tablets for example, there may be no Earpiece device @@ -155,7 +155,7 @@ class IncomingCallTutorialContext : ObservableObject } /* If we wanted to route the audio to a bluetooth headset else if (audioDevice.type == AudioDevice.Type.Bluetooth) { - core.currentCall?.outputAudioDevice = audioDevice + core.currentCall?.outputAudioDevice = audioDevice }*/ } } diff --git a/ios/swift/3-OutgoingCall/OutgoingCall/ContentView.swift b/ios/swift/3-OutgoingCall/OutgoingCall/ContentView.swift index 68e1c20..51be0f8 100644 --- a/ios/swift/3-OutgoingCall/OutgoingCall/ContentView.swift +++ b/ios/swift/3-OutgoingCall/OutgoingCall/ContentView.swift @@ -10,7 +10,7 @@ import SwiftUI import linphonesw struct ContentView: View { - + @ObservedObject var tutorialContext : OutgoingCallTutorialContext func callStateString() -> String { @@ -21,24 +21,24 @@ struct ContentView: View { } } - var body: some View { - - VStack { - Group { - HStack { - Text("Username:") - .font(.title) + var body: some View { + + VStack { + Group { + HStack { + Text("Username:") + .font(.title) TextField("", text : $tutorialContext.username) - .textFieldStyle(RoundedBorderTextFieldStyle()) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } - HStack { - Text("Password:") - .font(.title) - TextField("", text : $tutorialContext.passwd) - .textFieldStyle(RoundedBorderTextFieldStyle()) + } + HStack { + Text("Password:") + .font(.title) + TextField("", text : $tutorialContext.passwd) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } + } HStack { Text("Domain:") .font(.title) @@ -47,37 +47,37 @@ struct ContentView: View { .disabled(tutorialContext.loggedIn) } Picker(selection: $tutorialContext.transportType, label: Text("Transport:")) { - Text("TLS").tag("TLS") - Text("TCP").tag("TCP") - Text("UDP").tag("UDP") + Text("TLS").tag("TLS") + Text("TCP").tag("TCP") + Text("UDP").tag("UDP") }.pickerStyle(SegmentedPickerStyle()).padding() - VStack { - HStack { - Button(action: { - if (self.tutorialContext.loggedIn) - { - self.tutorialContext.unregister() + VStack { + HStack { + Button(action: { + if (self.tutorialContext.loggedIn) + { + self.tutorialContext.unregister() self.tutorialContext.delete() - } else { - self.tutorialContext.login() - } - }) - { - Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") - .font(.largeTitle) - .foregroundColor(Color.white) + } else { + self.tutorialContext.login() + } + }) + { + Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") + .font(.largeTitle) + .foregroundColor(Color.white) .frame(width: 220.0, height: 90) - .background(Color.gray) - } - } - HStack { - Text("Login State : ") - .font(.footnote) - Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") - .font(.footnote) - .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) - }.padding(.top, 10.0) - } + .background(Color.gray) + } + } + HStack { + Text("Login State : ") + .font(.footnote) + Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") + .font(.footnote) + .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) + }.padding(.top, 10.0) + } VStack { HStack { Text("Call dest:") @@ -96,10 +96,10 @@ struct ContentView: View { }) { Text( (tutorialContext.isCallRunning) ? "End" : "Call") - .font(.largeTitle) - .foregroundColor(Color.white) - .frame(width: 180.0, height: 42.0) - .background(Color.gray) + .font(.largeTitle) + .foregroundColor(Color.white) + .frame(width: 180.0, height: 42.0) + .background(Color.gray) } HStack { Text(tutorialContext.isCallRunning ? "Running" : "") @@ -154,18 +154,18 @@ struct ContentView: View { } } }.padding(.top, 30) - } - Group { - Spacer() - Text("Core Version is \(tutorialContext.coreVersion)") - } - } - .padding() - } + } + Group { + Spacer() + Text("Core Version is \(tutorialContext.coreVersion)") + } + } + .padding() + } } struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView(tutorialContext: OutgoingCallTutorialContext()) - } + static var previews: some View { + ContentView(tutorialContext: OutgoingCallTutorialContext()) + } } diff --git a/ios/swift/3-OutgoingCall/OutgoingCall/OutgoingCall.swift b/ios/swift/3-OutgoingCall/OutgoingCall/OutgoingCall.swift index a5c221f..88fb0cd 100644 --- a/ios/swift/3-OutgoingCall/OutgoingCall/OutgoingCall.swift +++ b/ios/swift/3-OutgoingCall/OutgoingCall/OutgoingCall.swift @@ -10,15 +10,15 @@ import linphonesw class OutgoingCallTutorialContext : ObservableObject { - var mCore: Core! - @Published var coreVersion: String = Core.getVersion - - var mAccount: Account? + var mCore: Core! + @Published var coreVersion: String = Core.getVersion + + var mAccount: Account? var mCoreDelegate : CoreDelegate! - @Published var username : String = "user" - @Published var passwd : String = "pwd" + @Published var username : String = "user" + @Published var passwd : String = "pwd" @Published var domain : String = "sip.example.org" - @Published var loggedIn: Bool = false + @Published var loggedIn: Bool = false @Published var transportType : String = "TLS" // Outgoing call related variables @@ -28,11 +28,11 @@ class OutgoingCallTutorialContext : ObservableObject @Published var canChangeCamera : Bool = false @Published var remoteAddress : String = "sip:arguillq@sip.linphone.org" - init() - { + init() + { LoggingService.Instance.logLevel = LogLevel.Debug - try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) + try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) // Here we enable the video capture & display at Core level // It doesn't mean calls will be made with video automatically, // But it allows to use it later @@ -50,7 +50,7 @@ class OutgoingCallTutorialContext : ObservableObject // If the following property is enabled, it will automatically configure created call params with video enabled //core.videoActivationPolicy.automaticallyInitiate = true - try? mCore.start() + try? mCore.start() mCoreDelegate = CoreDelegateStub( onCallStateChanged: { (core: Core, call: Call, state: Call.State, message: String) in // This function will be called each time a call state changes, @@ -71,7 +71,7 @@ class OutgoingCallTutorialContext : ObservableObject // or after the ICE negotiation completes // Wait for the call to be connected before allowing a call update self.isCallRunning = true - + // Only enable toggle camera button if there is more than 1 camera // We check if core.videoDevicesList.size > 2 because of the fake camera with static image created by our SDK (see below) self.canChangeCamera = core.videoDevicesList.count > 2 @@ -89,7 +89,7 @@ class OutgoingCallTutorialContext : ObservableObject self.isCallRunning = false self.canChangeCamera = false } else if (state == .Error) { - + } }, onAccountRegistrationStateChanged: { (core: Core, account: Account, state: RegistrationState, message: String) in NSLog("New registration state is \(state) for user id \( String(describing: account.params?.identityAddress?.asString()))\n") @@ -100,8 +100,8 @@ class OutgoingCallTutorialContext : ObservableObject } }) mCore.addDelegate(delegate: mCoreDelegate) - } - + } + func login() { do { @@ -122,19 +122,19 @@ class OutgoingCallTutorialContext : ObservableObject mCore.addAuthInfo(info: authInfo) try mCore.addAccount(account: mAccount!) mCore.defaultAccount = mAccount - + } catch { NSLog(error.localizedDescription) } } - func unregister() - { + func unregister() + { if let account = mCore.defaultAccount { let params = account.params let clonedParams = params?.clone() clonedParams?.registerEnabled = false account.params = clonedParams } - } + } func delete() { if let account = mCore.defaultAccount { mCore.removeAccount(account: account) @@ -142,17 +142,17 @@ class OutgoingCallTutorialContext : ObservableObject mCore.clearAllAuthInfo() } } - + func outgoingCall() { do { // As for everything we need to get the SIP URI of the remote and convert it to an Address let remoteAddress = try Factory.Instance.createAddress(addr: remoteAddress) - + // We also need a CallParams object // Create call params expects a Call object for incoming calls, but for outgoing we must use null safely let params = try mCore.createCallParams(call: nil) - + // We can now configure it // Here we ask for no encryption but we could ask for ZRTP/SRTP/DTLS params.mediaEncryption = MediaEncryption.None @@ -169,10 +169,10 @@ class OutgoingCallTutorialContext : ObservableObject func terminateCall() { do { if (mCore.callsNb == 0) { return } - + // If the call state isn't paused, we can get it using core.currentCall let coreCall = (mCore.currentCall != nil) ? mCore.currentCall : mCore.calls[0] - + // Terminating a call is quite simple if let call = coreCall { try call.terminate() @@ -207,7 +207,7 @@ class OutgoingCallTutorialContext : ObservableObject do { // Currently used camera let currentDevice = mCore.videoDevice - + // Let's iterate over all camera available and choose another one for camera in mCore.videoDevicesList { // All devices will have a "Static picture" fake camera, and we don't want to use it @@ -223,7 +223,7 @@ class OutgoingCallTutorialContext : ObservableObject do { if (mCore.callsNb == 0) { return } let coreCall = (mCore.currentCall != nil) ? mCore.currentCall : mCore.calls[0] - + if let call = coreCall { if (call.state != Call.State.Paused && call.state != Call.State.Pausing) { // If our call isn't paused, let's pause it diff --git a/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitProviderDelegate.swift b/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitProviderDelegate.swift index 34e4a23..ea30585 100644 --- a/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitProviderDelegate.swift +++ b/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitProviderDelegate.swift @@ -14,53 +14,53 @@ import AVFoundation class CallKitProviderDelegate : NSObject { - private let provider: CXProvider - let mCallController = CXCallController() - var tutorialContext : CallKitExampleContext! - - var incomingCallUUID : UUID! - - init(context: CallKitExampleContext) - { - tutorialContext = context - let providerConfiguration = CXProviderConfiguration(localizedName: Bundle.main.infoDictionary!["CFBundleName"] as! String) - providerConfiguration.supportsVideo = true - providerConfiguration.supportedHandleTypes = [.generic] - - providerConfiguration.maximumCallsPerCallGroup = 1 - providerConfiguration.maximumCallGroups = 1 - - provider = CXProvider(configuration: providerConfiguration) - super.init() - provider.setDelegate(self, queue: nil) // The CXProvider delegate will trigger CallKit related callbacks - - } - - func incomingCall() - { - incomingCallUUID = UUID() - let update = CXCallUpdate() - update.remoteHandle = CXHandle(type:.generic, value: tutorialContext.incomingCallName) - - provider.reportNewIncomingCall(with: incomingCallUUID, update: update, completion: { error in }) // Report to CallKit a call is incoming - } - - func stopCall() - { - let endCallAction = CXEndCallAction(call: incomingCallUUID) - let transaction = CXTransaction(action: endCallAction) - - mCallController.request(transaction, completion: { error in }) // Report to CallKit a call must end - } - + private let provider: CXProvider + let mCallController = CXCallController() + var tutorialContext : CallKitExampleContext! + + var incomingCallUUID : UUID! + + init(context: CallKitExampleContext) + { + tutorialContext = context + let providerConfiguration = CXProviderConfiguration(localizedName: Bundle.main.infoDictionary!["CFBundleName"] as! String) + providerConfiguration.supportsVideo = true + providerConfiguration.supportedHandleTypes = [.generic] + + providerConfiguration.maximumCallsPerCallGroup = 1 + providerConfiguration.maximumCallGroups = 1 + + provider = CXProvider(configuration: providerConfiguration) + super.init() + provider.setDelegate(self, queue: nil) // The CXProvider delegate will trigger CallKit related callbacks + + } + + func incomingCall() + { + incomingCallUUID = UUID() + let update = CXCallUpdate() + update.remoteHandle = CXHandle(type:.generic, value: tutorialContext.incomingCallName) + + provider.reportNewIncomingCall(with: incomingCallUUID, update: update, completion: { error in }) // Report to CallKit a call is incoming + } + + func stopCall() + { + let endCallAction = CXEndCallAction(call: incomingCallUUID) + let transaction = CXTransaction(action: endCallAction) + + mCallController.request(transaction, completion: { error in }) // Report to CallKit a call must end + } + } // In this extension, we implement the action we want to be done when CallKit is notified of something. // This can happen through the CallKit GUI in the app, or directly in the code (see, incomingCall(), stopCall() functions above) extension CallKitProviderDelegate: CXProviderDelegate { - - func provider(_ provider: CXProvider, perform action: CXEndCallAction) { + + func provider(_ provider: CXProvider, perform action: CXEndCallAction) { do { if (tutorialContext.mCall?.state != .End && tutorialContext.mCall?.state != .Released) { try tutorialContext.mCall?.terminate() @@ -69,31 +69,31 @@ extension CallKitProviderDelegate: CXProviderDelegate { tutorialContext.isCallRunning = false tutorialContext.isCallIncoming = false - action.fulfill() - } - - func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { - do { - try tutorialContext.mCall?.accept() - tutorialContext.isCallRunning = true - } catch { - print(error) - } - action.fulfill() - } - - func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {} - func provider(_ provider: CXProvider, perform action: CXStartCallAction) {} - func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {} - func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {} - func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {} - func providerDidReset(_ provider: CXProvider) {} - - func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { - tutorialContext.mCore.activateAudioSession(actived: true) - } - - func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { - tutorialContext.mCore.activateAudioSession(actived: false) - } + action.fulfill() + } + + func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { + do { + try tutorialContext.mCall?.accept() + tutorialContext.isCallRunning = true + } catch { + print(error) + } + action.fulfill() + } + + func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {} + func provider(_ provider: CXProvider, perform action: CXStartCallAction) {} + func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {} + func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {} + func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {} + func providerDidReset(_ provider: CXProvider) {} + + func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { + tutorialContext.mCore.activateAudioSession(actived: true) + } + + func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { + tutorialContext.mCore.activateAudioSession(actived: false) + } } diff --git a/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitTutorial.swift b/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitTutorial.swift index f7b36cf..b02b028 100644 --- a/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitTutorial.swift +++ b/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitTutorial.swift @@ -11,14 +11,14 @@ import AVFoundation class CallKitExampleContext : ObservableObject { - var mCore: Core! - @Published var coreVersion: String = Core.getVersion - + var mCore: Core! + @Published var coreVersion: String = Core.getVersion + var mAccount: Account? var mCoreDelegate : CoreDelegate! - @Published var username : String = "quentindev" - @Published var passwd : String = "dev" - @Published var domain : String = "sip.linphone.org" + @Published var username : String = "user" + @Published var passwd : String = "pwd" + @Published var domain : String = "sip.example.org" @Published var loggedIn: Bool = false @Published var transportType : String = "TLS" @@ -35,8 +35,8 @@ class CallKitExampleContext : ObservableObject var mProviderDelegate : CallKitProviderDelegate! var mCallAlreadyStopped : Bool = false; - init() - { + init() + { LoggingService.Instance.logLevel = LogLevel.Debug let factory = Factory.Instance @@ -92,8 +92,8 @@ class CallKitExampleContext : ObservableObject } }) mCore.addDelegate(delegate: mCoreDelegate) - } - + } + func login() { do { @@ -118,7 +118,7 @@ class CallKitExampleContext : ObservableObject mCore.addAuthInfo(info: authInfo) try mCore.addAccount(account: mAccount!) mCore.defaultAccount = mAccount - + } catch { NSLog(error.localizedDescription) } } diff --git a/ios/swift/4-CallKitTutorial/CallKitTutorial/ContentView.swift b/ios/swift/4-CallKitTutorial/CallKitTutorial/ContentView.swift index ac8166d..8626bd0 100644 --- a/ios/swift/4-CallKitTutorial/CallKitTutorial/ContentView.swift +++ b/ios/swift/4-CallKitTutorial/CallKitTutorial/ContentView.swift @@ -48,9 +48,9 @@ struct ContentView: View { .disabled(tutorialContext.loggedIn) } Picker(selection: $tutorialContext.transportType, label: Text("Transport:")) { - Text("TLS").tag("TLS") - Text("TCP").tag("TCP") - Text("UDP").tag("UDP") + Text("TLS").tag("TLS") + Text("TCP").tag("TCP") + Text("UDP").tag("UDP") }.pickerStyle(SegmentedPickerStyle()).padding() VStack { HStack { diff --git a/ios/swift/5-BasicChat/BasicChat/BasicChat.swift b/ios/swift/5-BasicChat/BasicChat/BasicChat.swift index e199411..7117985 100644 --- a/ios/swift/5-BasicChat/BasicChat/BasicChat.swift +++ b/ios/swift/5-BasicChat/BasicChat/BasicChat.swift @@ -12,14 +12,14 @@ import linphonesw class BasicChatTutorialContext : ObservableObject { - var mCore: Core! - @Published var coreVersion: String = Core.getVersion - + var mCore: Core! + @Published var coreVersion: String = Core.getVersion + var mRegistrationDelegate : CoreDelegate! - @Published var username : String = "user" - @Published var passwd : String = "pwd" + @Published var username : String = "user" + @Published var passwd : String = "pwd" @Published var domain : String = "sip.example.org" - @Published var loggedIn: Bool = false + @Published var loggedIn: Bool = false @Published var transportType : String = "TLS" /*------------ Basic chat tutorial related variables -------*/ @@ -35,12 +35,12 @@ class BasicChatTutorialContext : ObservableObject var fileFolderUrl : URL! var fileUrl : URL! - init() - { + init() + { LoggingService.Instance.logLevel = LogLevel.Debug - try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) - try? mCore.start() + try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) + try? mCore.start() mRegistrationDelegate = CoreDelegateStub(onMessageReceived : { (core: Core, chatRoom: ChatRoom, message: ChatMessage) in // We will be called in this when a message is received @@ -58,7 +58,7 @@ class BasicChatTutorialContext : ObservableObject } // We will notify the sender the message has been read by us chatRoom.markAsRead() - + for content in message.contents { if (content.isFileTransfer) { self.mLastFileMessageReceived = message @@ -98,8 +98,8 @@ class BasicChatTutorialContext : ObservableObject }catch let error as NSError{ print("Unable to create d)irectory",error) } - } - + } + func login() { do { @@ -117,24 +117,24 @@ class BasicChatTutorialContext : ObservableObject try accountParams.setServeraddress(newValue: address) accountParams.registerEnabled = true let account = try mCore.createAccount(params: accountParams) - + mCore.addAuthInfo(info: authInfo) try mCore.addAccount(account: account) - + mCore.defaultAccount = account - + } catch { NSLog(error.localizedDescription) } } - func unregister() - { + func unregister() + { if let account = mCore.defaultAccount { let params = account.params let clonedParams = params?.clone() clonedParams?.registerEnabled = false account.params = clonedParams } - } + } func delete() { if let account = mCore.defaultAccount { mCore.removeAccount(account: account) @@ -153,7 +153,7 @@ class BasicChatTutorialContext : ObservableObject params.backend = ChatRoomBackend.Basic params.encryptionEnabled = false params.groupEnabled = false - + if (params.isValid) { // We also need the SIP address of the person we will chat with let remote = try Factory.Instance.createAddress(addr: remoteAddress) @@ -166,7 +166,7 @@ class BasicChatTutorialContext : ObservableObject } } catch { NSLog(error.localizedDescription) } } - + func sendMessage() { do { if (mChatroom == nil) { @@ -176,50 +176,50 @@ class BasicChatTutorialContext : ObservableObject mChatMessage = nil // We need to create a ChatMessage object using the ChatRoom mChatMessage = try mChatroom!.createMessageFromUtf8(message: msgToSend) - + // Then we can send it, progress will be notified using the onMsgStateChanged callback mChatMessage!.addDelegate(delegate: mChatMessageDelegate) - + // Send the message mChatMessage!.send() - + // Clear the message input field msgToSend.removeAll() } catch { NSLog(error.localizedDescription) } } - + func sendFile() { do { if (mChatroom == nil) { // We need a ChatRoom object to send chat messages in it, so let's create it if it hasn't been done yet createBasicChatRoom() } - + // We need to create a Content for our file transfer let content = try Factory.Instance.createContent() // Every content needs a content type & subtype content.name = "file_to_transfer.txt" content.type = "text" content.subtype = "plain" - + // The simplest way to upload a file is to provide it's path content.filePath = fileUrl.path - + // We need to create a ChatMessage object using the ChatRoom let chatMessage = try mChatroom!.createFileTransferMessage(initialContent: content) - + // Then we can send it, progress will be notified using the onMsgStateChanged callback chatMessage.addDelegate(delegate: mChatMessageDelegate) - + // Ensure a file sharing server URL is correctly set in the Core mCore.fileTransferServer = "https://www.linphone.org:444/lft.php" - + // Send the message chatMessage.send() } catch { NSLog(error.localizedDescription) } } - + func downloadLastFileMessage() { if let message = mLastFileMessageReceived { diff --git a/ios/swift/5-BasicChat/BasicChat/ContentView.swift b/ios/swift/5-BasicChat/BasicChat/ContentView.swift index 41dc65c..fc32681 100644 --- a/ios/swift/5-BasicChat/BasicChat/ContentView.swift +++ b/ios/swift/5-BasicChat/BasicChat/ContentView.swift @@ -21,27 +21,27 @@ struct ActivityIndicator: UIViewRepresentable { } struct ContentView: View { - + @ObservedObject var tutorialContext : BasicChatTutorialContext - - var body: some View { - - VStack { - Group { - HStack { - Text("Username:") - .font(.title) + + var body: some View { + + VStack { + Group { + HStack { + Text("Username:") + .font(.title) TextField("", text : $tutorialContext.username) - .textFieldStyle(RoundedBorderTextFieldStyle()) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } - HStack { - Text("Password:") - .font(.title) - TextField("", text : $tutorialContext.passwd) - .textFieldStyle(RoundedBorderTextFieldStyle()) + } + HStack { + Text("Password:") + .font(.title) + TextField("", text : $tutorialContext.passwd) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } + } HStack { Text("Domain:") .font(.title) @@ -50,37 +50,37 @@ struct ContentView: View { .disabled(tutorialContext.loggedIn) } Picker(selection: $tutorialContext.transportType, label: Text("Transport:")) { - Text("TLS").tag("TLS") - Text("TCP").tag("TCP") - Text("UDP").tag("UDP") + Text("TLS").tag("TLS") + Text("TCP").tag("TCP") + Text("UDP").tag("UDP") }.pickerStyle(SegmentedPickerStyle()).padding() - VStack { - HStack { - Button(action: { - if (self.tutorialContext.loggedIn) - { - self.tutorialContext.unregister() + VStack { + HStack { + Button(action: { + if (self.tutorialContext.loggedIn) + { + self.tutorialContext.unregister() self.tutorialContext.delete() - } else { - self.tutorialContext.login() - } - }) - { - Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") - .font(.largeTitle) - .foregroundColor(Color.white) + } else { + self.tutorialContext.login() + } + }) + { + Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") + .font(.largeTitle) + .foregroundColor(Color.white) .frame(width: 220.0, height: 90) - .background(Color.gray) - } - - } - HStack { - Text("Login State : ") - .font(.footnote) - Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") - .font(.footnote) - .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) - }.padding(.top, 10.0) + .background(Color.gray) + } + + } + HStack { + Text("Login State : ") + .font(.footnote) + Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") + .font(.footnote) + .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) + }.padding(.top, 10.0) HStack { Text("Chat with:") TextField("", text : $tutorialContext.remoteAddress) @@ -109,36 +109,36 @@ struct ContentView: View { Button(action: tutorialContext.sendFile) { Text("Send example \n file") - .foregroundColor(Color.white) - .multilineTextAlignment(.center) - .frame(width: 120.0, height: 50.0) - .background(Color.gray) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .frame(width: 120.0, height: 50.0) + .background(Color.gray) } Button(action: tutorialContext.downloadLastFileMessage) { Text("Download last files \n received") - .foregroundColor(Color.white) - .multilineTextAlignment(.center) - .frame(width: 150.0, height: 50.0) - .background(Color.gray) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .frame(width: 150.0, height: 50.0) + .background(Color.gray) }.disabled(tutorialContext.mLastFileMessageReceived == nil) if (tutorialContext.isDownloading) { ActivityIndicator() } } - } - } - Group { - Spacer() - Text("Core Version is \(tutorialContext.coreVersion)") - } - } - .padding() - } + } + } + Group { + Spacer() + Text("Core Version is \(tutorialContext.coreVersion)") + } + } + .padding() + } } struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView(tutorialContext: BasicChatTutorialContext()) - } + static var previews: some View { + ContentView(tutorialContext: BasicChatTutorialContext()) + } } diff --git a/ios/swift/6-AdvancedChat/AdvancedChat/AdvancedChat.swift b/ios/swift/6-AdvancedChat/AdvancedChat/AdvancedChat.swift index b987e0d..62914b7 100644 --- a/ios/swift/6-AdvancedChat/AdvancedChat/AdvancedChat.swift +++ b/ios/swift/6-AdvancedChat/AdvancedChat/AdvancedChat.swift @@ -12,14 +12,14 @@ import linphonesw class AdvancedChatTutorialContext : ObservableObject { - var mCore: Core! - @Published var coreVersion: String = Core.getVersion - + var mCore: Core! + @Published var coreVersion: String = Core.getVersion + var mRegistrationDelegate : CoreDelegate! - @Published var username : String = "user" - @Published var passwd : String = "pwd" + @Published var username : String = "user" + @Published var passwd : String = "pwd" @Published var domain : String = "sip.example.org" - @Published var loggedIn: Bool = false + @Published var loggedIn: Bool = false @Published var transportType : String = "TLS" /*------------ Advanced chat tutorial related variables -------*/ @@ -36,12 +36,12 @@ class AdvancedChatTutorialContext : ObservableObject var fileFolderUrl : URL! var fileUrl : URL! - init() - { + init() + { LoggingService.Instance.logLevel = LogLevel.Debug - try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) - try? mCore.start() + try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil) + try? mCore.start() mRegistrationDelegate = CoreDelegateStub(onMessageReceived : { (core: Core, chatRoom: ChatRoom, message: ChatMessage) in if (self.mChatroom == nil) { @@ -60,7 +60,7 @@ class AdvancedChatTutorialContext : ObservableObject } chatRoom.markAsRead() - + for content in message.contents { if (content.isFileTransfer) { self.mLastFileMessageReceived = message @@ -124,8 +124,8 @@ class AdvancedChatTutorialContext : ObservableObject }catch let error as NSError{ print("Unable to create d)irectory",error) } - } - + } + func login() { do { @@ -151,21 +151,21 @@ class AdvancedChatTutorialContext : ObservableObject let account = try mCore.createAccount(params: accountParams) try mCore.addAccount(account: account) mCore.defaultAccount = account - + // We also need a LIME X3DH server URL configured for end to end encryption mCore.limeX3DhServerUrl = "https://lime.linphone.org/lime-server/lime-server.php" } catch { NSLog(error.localizedDescription) } } - func unregister() - { + func unregister() + { if let account = mCore.defaultAccount { let params = account.params let clonedParams = params?.clone() clonedParams?.registerEnabled = false account.params = clonedParams } - } + } func delete() { if let account = mCore.defaultAccount { mCore.removeAccount(account: account) @@ -174,26 +174,26 @@ class AdvancedChatTutorialContext : ObservableObject } } - + func createFlexisipChatRoom() { do { // In this tutorial we will create a Flexisip one-to-one chat room with end-to-end encryption // For it to work, the proxy server we connect to must be an instance of Flexisip // And we must have configured on the Account a conference-factory URI let params = try mCore.createDefaultChatRoomParams() - + // We won't create a group chat, only a 1-1 with advanced features such as end-to-end encryption params.backend = ChatRoomBackend.FlexisipChat params.groupEnabled = false - + // We will rely on LIME encryption backend (we must have configured the core.limex3dhServerUrl first) params.encryptionEnabled = true params.encryptionBackend = ChatRoomEncryptionBackend.Lime - + // A flexisip chat room must have a subject // But as we are doing a 1-1 chat room here we won't display it, so we can set whatever we want params.subject = "dummy subject" - + if (params.isValid) { // We also need the SIP address of the person we will chat with let remote = try Factory.Instance.createAddress(addr: remoteAddress) @@ -204,7 +204,7 @@ class AdvancedChatTutorialContext : ObservableObject // If chat room isn't created yet, wait for it to go in state Created // as Flexisip chat room creation process is asynchronous mChatroom!.addDelegate(delegate: mChatroomDelegate) - + // Chat room may already be created (for example if you logged in with an account for which the chat room already exists) if (mChatroom!.state == ChatRoom.State.Created) { enableEphemeral() @@ -212,8 +212,8 @@ class AdvancedChatTutorialContext : ObservableObject } } } catch { NSLog(error.localizedDescription) } - } - + } + func sendMessage() { do { if (mChatroom == nil) { @@ -226,13 +226,13 @@ class AdvancedChatTutorialContext : ObservableObject msgToSend.removeAll() } catch { NSLog(error.localizedDescription) } } - + func sendFile() { do { if (mChatroom == nil) { createFlexisipChatRoom() } - + let content = try Factory.Instance.createContent() content.name = "file_to_transfer.txt" content.type = "text" @@ -245,7 +245,7 @@ class AdvancedChatTutorialContext : ObservableObject } catch { NSLog(error.localizedDescription) } } - + func downloadLastFileMessage() { if let message = mLastFileMessageReceived { for content in message.contents { diff --git a/ios/swift/6-AdvancedChat/AdvancedChat/ContentView.swift b/ios/swift/6-AdvancedChat/AdvancedChat/ContentView.swift index 93d2437..9125a79 100644 --- a/ios/swift/6-AdvancedChat/AdvancedChat/ContentView.swift +++ b/ios/swift/6-AdvancedChat/AdvancedChat/ContentView.swift @@ -21,27 +21,27 @@ struct ActivityIndicator: UIViewRepresentable { } struct ContentView: View { - + @ObservedObject var tutorialContext : AdvancedChatTutorialContext - - var body: some View { - - VStack { - Group { - HStack { - Text("Username:") - .font(.title) + + var body: some View { + + VStack { + Group { + HStack { + Text("Username:") + .font(.title) TextField("", text : $tutorialContext.username) - .textFieldStyle(RoundedBorderTextFieldStyle()) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } - HStack { - Text("Password:") - .font(.title) - TextField("", text : $tutorialContext.passwd) - .textFieldStyle(RoundedBorderTextFieldStyle()) + } + HStack { + Text("Password:") + .font(.title) + TextField("", text : $tutorialContext.passwd) + .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(tutorialContext.loggedIn) - } + } HStack { Text("Domain:") .font(.title) @@ -50,37 +50,37 @@ struct ContentView: View { .disabled(tutorialContext.loggedIn) } Picker(selection: $tutorialContext.transportType, label: Text("Transport:")) { - Text("TLS").tag("TLS") - Text("TCP").tag("TCP") - Text("UDP").tag("UDP") + Text("TLS").tag("TLS") + Text("TCP").tag("TCP") + Text("UDP").tag("UDP") }.pickerStyle(SegmentedPickerStyle()).padding() - VStack { - HStack { - Button(action: { - if (self.tutorialContext.loggedIn) - { - self.tutorialContext.unregister() + VStack { + HStack { + Button(action: { + if (self.tutorialContext.loggedIn) + { + self.tutorialContext.unregister() self.tutorialContext.delete() - } else { - self.tutorialContext.login() - } - }) - { - Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") - .font(.largeTitle) - .foregroundColor(Color.white) + } else { + self.tutorialContext.login() + } + }) + { + Text(tutorialContext.loggedIn ? "Log out & \ndelete account" : "Create & \nlog in account") + .font(.largeTitle) + .foregroundColor(Color.white) .frame(width: 220.0, height: 90) - .background(Color.gray) - } - - } - HStack { - Text("Login State : ") - .font(.footnote) - Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") - .font(.footnote) - .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) - }.padding(.top, 10.0) + .background(Color.gray) + } + + } + HStack { + Text("Login State : ") + .font(.footnote) + Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered") + .font(.footnote) + .foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black) + }.padding(.top, 10.0) HStack { Text("Chat with:") TextField("", text : $tutorialContext.remoteAddress) @@ -109,36 +109,36 @@ struct ContentView: View { Button(action: tutorialContext.sendFile) { Text("Send example \n file") - .foregroundColor(Color.white) - .multilineTextAlignment(.center) - .frame(width: 120.0, height: 50.0) - .background(Color.gray) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .frame(width: 120.0, height: 50.0) + .background(Color.gray) } Button(action: tutorialContext.downloadLastFileMessage) { Text("Download last files \n received") - .foregroundColor(Color.white) - .multilineTextAlignment(.center) - .frame(width: 150.0, height: 50.0) - .background(Color.gray) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .frame(width: 150.0, height: 50.0) + .background(Color.gray) }.disabled(tutorialContext.mLastFileMessageReceived == nil) if (tutorialContext.isDownloading) { ActivityIndicator() } } - } - } - Group { - Spacer() - Text("Core Version is \(tutorialContext.coreVersion)") - } - } - .padding() - } + } + } + Group { + Spacer() + Text("Core Version is \(tutorialContext.coreVersion)") + } + } + .padding() + } } struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView(tutorialContext: AdvancedChatTutorialContext()) - } + static var previews: some View { + ContentView(tutorialContext: AdvancedChatTutorialContext()) + } }