diff --git a/.gitignore b/.gitignore
index fb14950..227cf34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,17 @@ java/build
java/captures
java/.externalNativeBuild
java/.cxx
+
+
+# C# / UWP Files / VS Specifiq files
+.vs/
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+*UpgradeLog*.htm
+AppPackages
+
+[Oo]bj/
+[Bb]in/
diff --git a/.gitlab-ci-files/job-windows.yml b/.gitlab-ci-files/job-windows.yml
new file mode 100644
index 0000000..ee61bea
--- /dev/null
+++ b/.gitlab-ci-files/job-windows.yml
@@ -0,0 +1,13 @@
+job-windows:
+
+ stage: build
+ tags: [ "windows" ]
+ script:
+ # Build VS project
+ #Remove MinGW of MSYS from PATH and add MINGW_TYPE for MSYS2
+ - echo %PATH%
+ - cd uwp\cs
+ - |
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\msbuild.exe" -t:restore -verbosity:minimal
+ - |
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\msbuild.exe" TutorialsCS.sln /m /property:Configuration=Release /property:Platform=x64 -verbosity:quiet
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 73d7ee9..43291ea 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -11,6 +11,7 @@
include:
- '.gitlab-ci-files/job-android.yml'
+ - '.gitlab-ci-files/job-windows.yml'
stages:
- build
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..8ab5af7
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,43 @@
+Linphone SDK tutorials
+====================
+
+This repository holds tutorials explaining how to set up [Linphone-SDK](https://gitlab.linphone.org/BC/public/linphone-sdk/)
+in a Android / iOS / desktop projects and use it to implement some simple features.
+
+## License
+
+Copyright © Belledonne Communications
+
+Tutorials are published under [GNU/GPLv3 license](https://www.gnu.org/licenses/gpl-3.0.en.html), for free (open source).
+Please make sure that you understand and agree with the terms of this license before using it (see LICENSE.txt file for details).
+
+## Android
+
+Even though Linphone's APIs are available in Java, Android tutorials have been created in Kotlin as it is the current standard for Android apps.
+
+Linphone-SDK will be downloaded automatically by gradle from our [Maven repository](https://linphone.org/maven_repository/org/linphone/linphone-sdk-android/).
+
+## iOS
+
+In the same way, iOS tutorials are written in Swift but the same can be achieved through Objective-C.
+
+Linphone-SDK binaries are fetched from our [Cocoapods repository](https://gitlab.linphone.org/BC/public/podspec.git) for iOS.
+
+## Desktop
+
+Desktop tutorials are in C#, leveraging on our Nuget packaging.
+
+## Additional resources
+
+All tutorials require a SIP account to function, and if you don't have one you can create as many as you want and for free using our [free SIP service](https://subscribe.linphone.org/).
+
+You can check our [website](https://linphone.org/) for news, supported RFCs, licensing services, etc...
+
+Full API documentation is available for all of our supported languages:
+* [C++](http://linphone.org/snapshots/docs/liblinphone/latest/c++)
+* [C#](http://linphone.org/snapshots/docs/liblinphone/latest/cs)
+* [Java](http://linphone.org/snapshots/docs/liblinphone/latest/java)
+* [Swift](http://linphone.org/snapshots/docs/liblinphone/latest/swift)
+* [C](http://linphone.org/snapshots/docs/liblinphone/latest/c)
+
+Finally we also have a [wiki](https://wiki.linphone.org/xwiki/wiki/public/view/Main/) with articles on various subjects.
\ No newline at end of file
diff --git a/uwp/cs/00_HelloWorld/00_HelloWorld.csproj b/uwp/cs/00_HelloWorld/00_HelloWorld.csproj
new file mode 100644
index 0000000..0327b75
--- /dev/null
+++ b/uwp/cs/00_HelloWorld/00_HelloWorld.csproj
@@ -0,0 +1,174 @@
+
+
+
+
+ Debug
+ x86
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}
+ AppContainerExe
+ Properties
+ _00_HelloWorld
+ 00_HelloWorld
+ en-US
+ UAP
+ 10.0.17763.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ true
+ false
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+ App.xaml
+
+
+ MainPage.xaml
+
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ 5.1.0-alpha.56
+
+
+ 6.2.11
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/00_HelloWorld/App.xaml b/uwp/cs/00_HelloWorld/App.xaml
new file mode 100644
index 0000000..83734b6
--- /dev/null
+++ b/uwp/cs/00_HelloWorld/App.xaml
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/uwp/cs/00_HelloWorld/App.xaml.cs b/uwp/cs/00_HelloWorld/App.xaml.cs
new file mode 100644
index 0000000..3382a8d
--- /dev/null
+++ b/uwp/cs/00_HelloWorld/App.xaml.cs
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _00_HelloWorld
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ if (e.PrelaunchActivated == false)
+ {
+ if (rootFrame.Content == null)
+ {
+ // When the navigation stack isn't restored navigate to the first page,
+ // configuring the new page by passing required information as a navigation
+ // parameter
+ rootFrame.Navigate(typeof(MainPage), e.Arguments);
+ }
+ // Ensure the current window is active
+ Window.Current.Activate();
+ }
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/00_HelloWorld/Assets/LockScreenLogo.scale-200.png b/uwp/cs/00_HelloWorld/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/uwp/cs/00_HelloWorld/Assets/LockScreenLogo.scale-200.png differ
diff --git a/uwp/cs/00_HelloWorld/Assets/SplashScreen.scale-200.png b/uwp/cs/00_HelloWorld/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/uwp/cs/00_HelloWorld/Assets/SplashScreen.scale-200.png differ
diff --git a/uwp/cs/00_HelloWorld/Assets/Square150x150Logo.scale-200.png b/uwp/cs/00_HelloWorld/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/uwp/cs/00_HelloWorld/Assets/Square150x150Logo.scale-200.png differ
diff --git a/uwp/cs/00_HelloWorld/Assets/Square44x44Logo.scale-200.png b/uwp/cs/00_HelloWorld/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/uwp/cs/00_HelloWorld/Assets/Square44x44Logo.scale-200.png differ
diff --git a/uwp/cs/00_HelloWorld/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/uwp/cs/00_HelloWorld/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/uwp/cs/00_HelloWorld/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/uwp/cs/00_HelloWorld/Assets/StoreLogo.png b/uwp/cs/00_HelloWorld/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/uwp/cs/00_HelloWorld/Assets/StoreLogo.png differ
diff --git a/uwp/cs/00_HelloWorld/Assets/Wide310x150Logo.scale-200.png b/uwp/cs/00_HelloWorld/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/uwp/cs/00_HelloWorld/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/uwp/cs/00_HelloWorld/MainPage.xaml b/uwp/cs/00_HelloWorld/MainPage.xaml
new file mode 100644
index 0000000..2165b3f
--- /dev/null
+++ b/uwp/cs/00_HelloWorld/MainPage.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/00_HelloWorld/MainPage.xaml.cs b/uwp/cs/00_HelloWorld/MainPage.xaml.cs
new file mode 100644
index 0000000..434dc16
--- /dev/null
+++ b/uwp/cs/00_HelloWorld/MainPage.xaml.cs
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using Windows.UI.Xaml.Controls;
+
+namespace _00_HelloWorld
+{
+ ///
+ /// A really simple page to do a "HelloWorld" with LinphoneSDK x UWP
+ ///
+ public sealed partial class MainPage : Page
+ {
+ private Core StoredCore { get; set; }
+
+ private LoggingService LoggingService { get; set; }
+
+ public string HelloText { get; set; } = "Hello world, Linphone core version is ";
+
+ public MainPage()
+ {
+ this.InitializeComponent();
+
+ // Core is the main object of the SDK. You can't do much without it
+
+ // Some configuration can be done before the Core is created, for example enable debug logs.
+ LoggingService = LoggingService.Instance;
+ LoggingService.LogLevel = LogLevel.Debug;
+ // And here you set the implementation of the delegate method called every time the Linphone SDK log something, see OnLog.
+ LoggingService.Listener.OnLogMessageWritten = OnLog;
+
+ // To create a Core, we need the instance of the Factory.
+ Factory factory = Factory.Instance;
+
+ // Some configuration can be done on the factory before the Core is created, for example enable setting resources Path. This
+ // one is mandatory
+ string assetsPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share");
+ factory.TopResourcesDir = assetsPath;
+ factory.DataResourcesDir = assetsPath;
+ factory.SoundResourcesDir = Path.Combine(assetsPath, "sounds", "linphone");
+ factory.RingResourcesDir = Path.Combine(factory.SoundResourcesDir, "rings");
+ factory.ImageResourcesDir = Path.Combine(assetsPath, "images");
+ factory.MspluginsDir = ".";
+
+ // Your Core can use up to 2 configuration files, but that isn't mandatory.
+ // The third parameter is the application context, he isn't mandatory when working
+ // with UWP, he is mandatory in an Android context for example.
+ // You can now create your Core object :
+ Core core = factory.CreateCore("", "", IntPtr.Zero);
+
+ // Once you got your core you can start to do a lot of things.
+ HelloText += Core.Version;
+
+ // You should store the Core to keep a reference on it at all times while your app is alive.
+ // A good solution for that is either subclass the Application object or create a Service.
+ StoredCore = core;
+ }
+
+ ///
+ /// Simple function to console log everything the Linphone SDK logs.
+ /// You should modify this method to match your logging habits.
+ ///
+ private void OnLog(LoggingService logService, string domain, LogLevel lev, string message)
+ {
+ StringBuilder builder = new StringBuilder();
+ _ = builder.Append("Linphone-[").Append(lev.ToString()).Append("](").Append(domain).Append(")").Append(message);
+ Debug.WriteLine(builder.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/00_HelloWorld/Package.appxmanifest b/uwp/cs/00_HelloWorld/Package.appxmanifest
new file mode 100644
index 0000000..aefe7f0
--- /dev/null
+++ b/uwp/cs/00_HelloWorld/Package.appxmanifest
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+ 00_HelloWorld
+ Anthony
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UTF-8
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/00_HelloWorld/Properties/AssemblyInfo.cs b/uwp/cs/00_HelloWorld/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..73711ce
--- /dev/null
+++ b/uwp/cs/00_HelloWorld/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("00_HelloWorld")]
+[assembly: AssemblyDescription("Hello world C# tutorial with LinphoneSDK")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Belledonne Communications")]
+[assembly: AssemblyProduct("Linphone")]
+[assembly: AssemblyCopyright("Copyright © 2020 Belledonne Communications")]
+[assembly: AssemblyTrademark("Linphone")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/uwp/cs/00_HelloWorld/Properties/Default.rd.xml b/uwp/cs/00_HelloWorld/Properties/Default.rd.xml
new file mode 100644
index 0000000..76edee8
--- /dev/null
+++ b/uwp/cs/00_HelloWorld/Properties/Default.rd.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/00_HelloWorld/Readme.md b/uwp/cs/00_HelloWorld/Readme.md
new file mode 100644
index 0000000..4ff2ab0
--- /dev/null
+++ b/uwp/cs/00_HelloWorld/Readme.md
@@ -0,0 +1,21 @@
+Linphone X UWP tutorial 00_HelloWorld
+======================================
+
+The first tutorial is just here to display a hello world app with the current Linphone's version number.
+
+Don't forget to install those NuGet packages :
+ - LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
+ - Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
+
+Main files :
+```
+00_HelloWorld
+│ README.md : you are here
+│ App.xaml(.cs) : Default Windows Application file, nothing special here
+│ MainPage.xaml(.cs) : This is were the magic happen,
+│ jump into this file to learn about Linphone core creation and how to display a hello world.
+│
+└───Assets : default UWP app assets
+ │ LockScreenLogo.scale-200.png
+ │ ...
+```
\ No newline at end of file
diff --git a/uwp/cs/01_AccountLogin/01_AccountLogin.csproj b/uwp/cs/01_AccountLogin/01_AccountLogin.csproj
new file mode 100644
index 0000000..b558eef
--- /dev/null
+++ b/uwp/cs/01_AccountLogin/01_AccountLogin.csproj
@@ -0,0 +1,174 @@
+
+
+
+
+ Debug
+ x86
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}
+ AppContainerExe
+ Properties
+ _01_AccountLogin
+ 01_AccountLogin
+ en-US
+ UAP
+ 10.0.17763.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ true
+ false
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+ App.xaml
+
+
+ MainPage.xaml
+
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ 5.1.0-alpha.56
+
+
+ 6.2.11
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/01_AccountLogin/App.xaml b/uwp/cs/01_AccountLogin/App.xaml
new file mode 100644
index 0000000..3783e3b
--- /dev/null
+++ b/uwp/cs/01_AccountLogin/App.xaml
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/uwp/cs/01_AccountLogin/App.xaml.cs b/uwp/cs/01_AccountLogin/App.xaml.cs
new file mode 100644
index 0000000..456f646
--- /dev/null
+++ b/uwp/cs/01_AccountLogin/App.xaml.cs
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _01_AccountLogin
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ if (e.PrelaunchActivated == false)
+ {
+ if (rootFrame.Content == null)
+ {
+ // When the navigation stack isn't restored navigate to the first page,
+ // configuring the new page by passing required information as a navigation
+ // parameter
+ rootFrame.Navigate(typeof(MainPage), e.Arguments);
+ }
+ // Ensure the current window is active
+ Window.Current.Activate();
+ }
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/01_AccountLogin/Assets/LockScreenLogo.scale-200.png b/uwp/cs/01_AccountLogin/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/uwp/cs/01_AccountLogin/Assets/LockScreenLogo.scale-200.png differ
diff --git a/uwp/cs/01_AccountLogin/Assets/SplashScreen.scale-200.png b/uwp/cs/01_AccountLogin/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/uwp/cs/01_AccountLogin/Assets/SplashScreen.scale-200.png differ
diff --git a/uwp/cs/01_AccountLogin/Assets/Square150x150Logo.scale-200.png b/uwp/cs/01_AccountLogin/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/uwp/cs/01_AccountLogin/Assets/Square150x150Logo.scale-200.png differ
diff --git a/uwp/cs/01_AccountLogin/Assets/Square44x44Logo.scale-200.png b/uwp/cs/01_AccountLogin/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/uwp/cs/01_AccountLogin/Assets/Square44x44Logo.scale-200.png differ
diff --git a/uwp/cs/01_AccountLogin/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/uwp/cs/01_AccountLogin/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/uwp/cs/01_AccountLogin/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/uwp/cs/01_AccountLogin/Assets/StoreLogo.png b/uwp/cs/01_AccountLogin/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/uwp/cs/01_AccountLogin/Assets/StoreLogo.png differ
diff --git a/uwp/cs/01_AccountLogin/Assets/Wide310x150Logo.scale-200.png b/uwp/cs/01_AccountLogin/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/uwp/cs/01_AccountLogin/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/uwp/cs/01_AccountLogin/MainPage.xaml b/uwp/cs/01_AccountLogin/MainPage.xaml
new file mode 100644
index 0000000..8d98246
--- /dev/null
+++ b/uwp/cs/01_AccountLogin/MainPage.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/01_AccountLogin/MainPage.xaml.cs b/uwp/cs/01_AccountLogin/MainPage.xaml.cs
new file mode 100644
index 0000000..2a135be
--- /dev/null
+++ b/uwp/cs/01_AccountLogin/MainPage.xaml.cs
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Threading;
+using Windows.Storage;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+
+namespace _01_AccountLogin
+{
+ ///
+ /// A really simple page for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class MainPage : Page
+ {
+ private Core StoredCore { get; set; }
+
+ private LoggingService LoggingService { get; set; }
+
+ private Timer Timer;
+
+ public MainPage()
+ {
+ InitializeComponent();
+
+ // Core is the main object of the SDK. You can't do much without it
+ // If you're not familiar with Linphone Core creation, see the 00_HelloWorld project.
+ LoggingService = LoggingService.Instance;
+ LoggingService.LogLevel = LogLevel.Debug;
+ LoggingService.Listener.OnLogMessageWritten = OnLog;
+
+ Factory factory = Factory.Instance;
+
+ string assetsPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets");
+ factory.TopResourcesDir = assetsPath;
+ factory.DataResourcesDir = assetsPath;
+ factory.SoundResourcesDir = Path.Combine(assetsPath, "sounds", "linphone");
+ factory.RingResourcesDir = Path.Combine(factory.SoundResourcesDir, "rings");
+ factory.ImageResourcesDir = Path.Combine(assetsPath, "images");
+ factory.MspluginsDir = ".";
+
+ Core core = factory.CreateCore("", "", IntPtr.Zero);
+
+ StoredCore = core;
+
+ // We need to indicate to the core where are stored the root ans user certificates, for future TLS exchange.
+ StoredCore.RootCa = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share", "Linphone", "rootca.pem");
+ StoredCore.UserCertificatesPath = ApplicationData.Current.LocalFolder.Path;
+
+ // In this tutorials we are going to log in and our registration state will change.
+ // Here we show you how to register a delegate method called every time the
+ // on OnAccountRegistrationStateChanged callback is triggered.
+ StoredCore.Listener.OnAccountRegistrationStateChanged += OnAccountRegistrationStateChanged;
+
+ // Start the core after setup, and before everything else.
+ StoredCore.Start();
+
+ // The method Iterate must be permanently called on our core.
+ // The Iterate method runs all the waiting backgrounds tasks and poll networks notifications.
+ // Here how to setup a function called every 20ms, 20ms after the Timer object instantiation.
+ // See OnTimedEvent for more informations.
+ Timer = new Timer(OnTimedEvent, null, 20, 20);
+
+ // Setup GUI
+ Identity.Text = "sip:";
+ Password.PlaceholderText = "myPasswd";
+ LogoutGuiChanges();
+
+ // Now use the GUI to log in, and see LogInClick to see how to handle login.
+ }
+
+ ///
+ /// Here we scheduled a callback to the Iterate method on the UI thread. While the
+ /// Linphone API calls are not thread safe, we ensure all our callbacks are done on the UI thread.
+ /// Doing this, we allow callbacks to manipulate UI without dispatcher.
+ ///
+ private async void OnTimedEvent(object state)
+ {
+ await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
+ {
+ StoredCore.Iterate();
+ });
+ }
+
+ ///
+ /// Called when you click on the "Login" button.
+ ///
+ private void LogInClick(object sender, RoutedEventArgs e)
+ {
+ if (LogIn.IsEnabled)
+ {
+ LogIn.IsEnabled = false;
+
+ // To configure a SIP account, we need an Account object and an AuthInfo object
+ // The first one is how to connect to the proxy server, the second one stores the credentials
+
+ // Here we are creating an AuthInfo object from the identity Address and password provided by the user.
+ Address address = Factory.Instance.CreateAddress(Identity.Text);
+ // The AuthInfo can be created from the Factory as it's only a data class
+ // userID is set to null as it's the same as the username in our case
+ // 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
+ AuthInfo authInfo = Factory.Instance.CreateAuthInfo(address.Username, "", Password.Password, "", "", address.Domain);
+ // And we add it to the Core
+ StoredCore.AddAuthInfo(authInfo);
+
+ // Then we create an AccountParams object.
+ // It contains the account informations needed by the core
+ AccountParams accountParams = StoredCore.CreateAccountParams();
+ // A SIP account is identified by an identity address that we can construct from the username and domain
+ accountParams.IdentityAddress = address;
+ // We also need to configure where the proxy server is located
+ Address serverAddr = Factory.Instance.CreateAddress("sip:" + address.Domain);
+ // We use the Address object to easily set the transport protocol
+ serverAddr.Transport = TlsRadio.IsChecked ?? false ? TransportType.Tls : TcpRadio.IsChecked ?? false ? TransportType.Tcp : TransportType.Udp;
+ accountParams.ServerAddress = serverAddr;
+ // If RegisterEnabled is set to true, when this account will be added to the core it will
+ // automatically try to connect.
+ accountParams.RegisterEnabled = true;
+
+ // We can now create an Account object from the AccountParams ...
+ Account account = StoredCore.CreateAccount(accountParams);
+ // ... and add it to the core, launching the connection process.
+ StoredCore.AddAccount(account);
+ // Also set the newly added account as default
+ StoredCore.DefaultAccount = account;
+ }
+ }
+
+ ///
+ /// Called when a key is pressed and released on the login page.
+ /// If you pressed "Enter", simulate a login click.
+ ///
+ private void GridKeyUp(object sender, KeyRoutedEventArgs e)
+ {
+ if (VirtualKey.Enter.Equals(e.Key))
+ {
+ LogInClick(null, null);
+ }
+ }
+
+ ///
+ /// Called when you click on the "Logout" button.
+ ///
+ private void LogOutClick(object sender, RoutedEventArgs e)
+ {
+ if (LogOut.IsEnabled)
+ {
+ LogOut.IsEnabled = false;
+
+ // Setting RegisterEnabled to false on a connected Account object will
+ // launch the logout action.
+ Account account = StoredCore.DefaultAccount;
+ if (account != null)
+ {
+ // BUT BE CAREFUL : the Params attribute of an account is read-only
+ // You MUST Clone it :
+ AccountParams accountParams = account.Params.Clone();
+ // Then you can modify the clone :
+ accountParams.RegisterEnabled = false;
+ // And finally setting the new Params value triggers the changes, here the logout.
+ account.Params = accountParams;
+ }
+ }
+ }
+
+ ///
+ /// This method is called every time the RegistrationState is updated by background core's actions.
+ /// In this example we use this to update the GUI.
+ ///
+ private void OnAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, string message)
+ {
+ RegistrationText.Text = "Your registration state is : " + state.ToString();
+ switch (state)
+ {
+ // If the Account was logged out, we clear the Core.
+ case RegistrationState.Cleared:
+ case RegistrationState.None:
+ StoredCore.ClearAllAuthInfo();
+ StoredCore.ClearAccounts();
+ LogoutGuiChanges();
+ break;
+
+ case RegistrationState.Ok:
+ LoginGuiChanges();
+ break;
+
+ case RegistrationState.Progress:
+ LoginInProgressGuiChanges();
+ break;
+
+ case RegistrationState.Failed:
+ LoginFailedChanges();
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ private void LogoutGuiChanges()
+ {
+ LogIn.IsEnabled = true;
+ LogOut.IsEnabled = false;
+ LoginText.Text = "You are logged out";
+ }
+
+ private void LoginFailedChanges()
+ {
+ LogIn.IsEnabled = true;
+ LogOut.IsEnabled = false;
+ LoginText.Text = "Login failed, try again";
+ }
+
+ private void LoginGuiChanges()
+ {
+ LogIn.IsEnabled = false;
+ LogOut.IsEnabled = true;
+ LoginText.Text = "You are logged in, with identity " + StoredCore.Identity + ".";
+ }
+
+ private void LoginInProgressGuiChanges()
+ {
+ LogIn.IsEnabled = false;
+ LogOut.IsEnabled = false;
+ LoginText.Text = "Login in progress, with identity " + StoredCore.Identity + ".";
+ }
+
+ private void OnLog(LoggingService logService, string domain, LogLevel lev, string message)
+ {
+ StringBuilder builder = new StringBuilder();
+ _ = builder.Append("Linphone-[").Append(lev.ToString()).Append("](").Append(domain).Append(")").Append(message);
+ Debug.WriteLine(builder.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/01_AccountLogin/Package.appxmanifest b/uwp/cs/01_AccountLogin/Package.appxmanifest
new file mode 100644
index 0000000..5f03c6b
--- /dev/null
+++ b/uwp/cs/01_AccountLogin/Package.appxmanifest
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+ 01_AccountLogin
+ Anthony
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UTF-8
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/01_AccountLogin/Properties/AssemblyInfo.cs b/uwp/cs/01_AccountLogin/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..f371a83
--- /dev/null
+++ b/uwp/cs/01_AccountLogin/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("01_AccountLogin")]
+[assembly: AssemblyDescription("Login C# tutorial with LinphoneSDK")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Belledonne Communications")]
+[assembly: AssemblyProduct("Linphone")]
+[assembly: AssemblyCopyright("Copyright © 2020 Belledonne Communications")]
+[assembly: AssemblyTrademark("Linphone")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/uwp/cs/01_AccountLogin/Properties/Default.rd.xml b/uwp/cs/01_AccountLogin/Properties/Default.rd.xml
new file mode 100644
index 0000000..76edee8
--- /dev/null
+++ b/uwp/cs/01_AccountLogin/Properties/Default.rd.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/01_AccountLogin/Readme.md b/uwp/cs/01_AccountLogin/Readme.md
new file mode 100644
index 0000000..b277949
--- /dev/null
+++ b/uwp/cs/01_AccountLogin/Readme.md
@@ -0,0 +1,17 @@
+Linphone X UWP tutorial 01_AccountLogin
+================================
+
+In this tutorial we present you the different steps to login and logout a SIP account.
+
+To first register an account go here : https://www.linphone.org/freesip/home
+
+Don't forget to install those NuGet packages :
+ - LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
+ - Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
+
+New/updated files to watch :
+```
+01_AccountLogin
+│ MainPage.xaml(.cs) : This time this page is a minimalist login page and it display your login status.
+│ Watch its code to understand how to login/out with LinphoneSDK.
+```
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/02_IncomingCall.csproj b/uwp/cs/02_IncomingCall/02_IncomingCall.csproj
new file mode 100644
index 0000000..2e581c5
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/02_IncomingCall.csproj
@@ -0,0 +1,192 @@
+
+
+
+
+ Debug
+ x86
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}
+ AppContainerExe
+ Properties
+ _02_IncomingCall
+ 02_IncomingCall
+ en-US
+ UAP
+ 10.0.19041.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ true
+ false
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+ App.xaml
+
+
+ CallsPage.xaml
+
+
+
+ LoginPage.xaml
+
+
+
+ NavigationRoot.xaml
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ 2.1.13
+
+
+ 5.1.0-alpha.56
+
+
+ 6.2.11
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/App.xaml b/uwp/cs/02_IncomingCall/App.xaml
new file mode 100644
index 0000000..4e09378
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/App.xaml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/App.xaml.cs b/uwp/cs/02_IncomingCall/App.xaml.cs
new file mode 100644
index 0000000..01c8be0
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/App.xaml.cs
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _02_IncomingCall.Service;
+using _02_IncomingCall.Views;
+using Linphone;
+using System;
+using System.Diagnostics;
+using System.Text;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _02_IncomingCall
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Start Linphone
+ LoggingService.Instance.LogLevel = LogLevel.Debug;
+ LoggingService.Instance.Listener.OnLogMessageWritten = OnLog;
+
+ CoreService.CoreStart(Windows.ApplicationModel.Core.CoreApplication.GetCurrentView().CoreWindow.Dispatcher);
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ if (e.PrelaunchActivated == false)
+ {
+ if (rootFrame.Content == null)
+ {
+ // When the navigation stack isn't restored navigate to the first page,
+ // configuring the new page by passing required information as a navigation
+ // parameter
+ rootFrame.Navigate(typeof(LoginPage), e.Arguments);
+ }
+ // Ensure the current window is active
+ Window.Current.Activate();
+ }
+ }
+
+ private void OnLog(LoggingService logService, string domain, LogLevel lev, string message)
+ {
+ StringBuilder builder = new StringBuilder();
+ _ = builder.Append("Linphone-[").Append(lev.ToString()).Append("](").Append(domain).Append(")").Append(message);
+ Debug.WriteLine(builder.ToString());
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Assets/LockScreenLogo.scale-200.png b/uwp/cs/02_IncomingCall/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/uwp/cs/02_IncomingCall/Assets/LockScreenLogo.scale-200.png differ
diff --git a/uwp/cs/02_IncomingCall/Assets/SplashScreen.scale-200.png b/uwp/cs/02_IncomingCall/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/uwp/cs/02_IncomingCall/Assets/SplashScreen.scale-200.png differ
diff --git a/uwp/cs/02_IncomingCall/Assets/Square150x150Logo.scale-200.png b/uwp/cs/02_IncomingCall/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/uwp/cs/02_IncomingCall/Assets/Square150x150Logo.scale-200.png differ
diff --git a/uwp/cs/02_IncomingCall/Assets/Square44x44Logo.scale-200.png b/uwp/cs/02_IncomingCall/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/uwp/cs/02_IncomingCall/Assets/Square44x44Logo.scale-200.png differ
diff --git a/uwp/cs/02_IncomingCall/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/uwp/cs/02_IncomingCall/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/uwp/cs/02_IncomingCall/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/uwp/cs/02_IncomingCall/Assets/StoreLogo.png b/uwp/cs/02_IncomingCall/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/uwp/cs/02_IncomingCall/Assets/StoreLogo.png differ
diff --git a/uwp/cs/02_IncomingCall/Assets/Wide310x150Logo.scale-200.png b/uwp/cs/02_IncomingCall/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/uwp/cs/02_IncomingCall/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/uwp/cs/02_IncomingCall/Package.appxmanifest b/uwp/cs/02_IncomingCall/Package.appxmanifest
new file mode 100644
index 0000000..3f2f507
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Package.appxmanifest
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+ 02_IncomingCall
+ Anthony
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Properties/AssemblyInfo.cs b/uwp/cs/02_IncomingCall/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..7c6d478
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("02_IncomingCall")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("02_IncomingCall")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Properties/Default.rd.xml b/uwp/cs/02_IncomingCall/Properties/Default.rd.xml
new file mode 100644
index 0000000..76edee8
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Properties/Default.rd.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Readme.md b/uwp/cs/02_IncomingCall/Readme.md
new file mode 100644
index 0000000..9e8151a
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Readme.md
@@ -0,0 +1,43 @@
+Linphone X UWP tutorial 02_IncomingCall
+========================================
+
+This time we are going to receive our first calls !
+
+Because the architecture of the first two tutorials were a bit too simple for a larger app we moved things a bit.
+All the code about the core (creation, iterate, log in...) is now in the class Service/CoreService.
+
+The page LoginPage is updated and now redirects to a new page (NavigationRoot) this page only contains a NavigationView.
+If you are note familiar with NavigationView you can take a look at [the NavigationView doc](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/navigationview),
+but this is not mandatory since it contains no Linphone code and is only here for navigation.
+
+By default the NavigationView load the new page CallsPage (the only one for now), on this page you can answer or decline incoming calls.
+
+If you don't have SIP friends to make tests we recommend you to install Linphone on your mobile device (Android or iOS) and to make calls to yourself.
+
+Don't forget to install those NuGet packages :
+ - LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
+ - Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
+
+New/updated files :
+
+```
+02_outgoing_call
+│
+│ Package.appxmanifest : For this step we added new capabilities : Microphone, VOIP calling
+│
+└───Service :
+│ │ CoreService.cs : A singleton service which contains the Linphone.Core.
+│ │ You can find here all the previous tutorial code and the new code
+│ │ for calls !
+│ │
+│
+│
+└───Views :
+│ │ CallsPage.xaml(.cs) : This is the new page where you can make calls.
+│ │ This is where you will find the new Linphone's uses.
+│ │
+│ │ LoginPage.xaml(.cs) : The same login page as the previous step, now in his own file.
+│ │
+│ │ NavigationRoot.xaml(.cs) : The new page containing the NavigationView and the main app Frame.
+│
+```
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Service/CoreService.cs b/uwp/cs/02_IncomingCall/Service/CoreService.cs
new file mode 100644
index 0000000..6be1a82
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Service/CoreService.cs
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Media.Audio;
+using Windows.Storage;
+using Windows.UI.Core;
+using static Linphone.CoreListener;
+
+namespace _02_IncomingCall.Service
+{
+ internal class CoreService
+ {
+ private Timer Timer;
+
+ private static readonly CoreService instance = new CoreService();
+
+ public static CoreService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private Core core;
+
+ public Core Core
+ {
+ get
+ {
+ if (core == null)
+ {
+ Factory factory = Factory.Instance;
+
+ string assetsPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share");
+ factory.TopResourcesDir = assetsPath;
+ factory.DataResourcesDir = assetsPath;
+ factory.SoundResourcesDir = Path.Combine(assetsPath, "sounds", "linphone");
+ factory.RingResourcesDir = Path.Combine(factory.SoundResourcesDir, "rings");
+ factory.ImageResourcesDir = Path.Combine(assetsPath, "images");
+ factory.MspluginsDir = ".";
+
+ core = factory.CreateCore("", "", IntPtr.Zero);
+
+ core.AudioPort = 7666;
+
+ core.RootCa = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share", "Linphone", "rootca.pem");
+ core.UserCertificatesPath = ApplicationData.Current.LocalFolder.Path;
+ }
+ return core;
+ }
+ }
+
+ public void CoreStart(CoreDispatcher dispatcher)
+ {
+ Core.Start();
+
+ Timer = new Timer(OnTimedEvent, dispatcher, 20, 20);
+ }
+
+ private async void OnTimedEvent(object state)
+ {
+ await ((CoreDispatcher)state).RunIdleAsync((args) =>
+ {
+ Core.Iterate();
+ });
+ }
+
+ public void AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged += myDelegate;
+ }
+
+ public void RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged -= myDelegate;
+ }
+
+ ///
+ /// Used to add a delegate for the OnCallStateChanged callback, using += allow you to register
+ /// multiples delegates.
+ ///
+ public void AddOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged += myDelegate;
+ }
+
+ public void RemoveOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged -= myDelegate;
+ }
+
+ public void LogIn(string identity, string password)
+ {
+ Address address = Factory.Instance.CreateAddress(identity);
+ AuthInfo authInfo = Factory.Instance.CreateAuthInfo(address.Username, "", password, "", "", address.Domain);
+ Core.AddAuthInfo(authInfo);
+
+ AccountParams accountParams = Core.CreateAccountParams();
+ accountParams.IdentityAddress = address;
+ string serverAddr = "sip:" + address.Domain + ";transport=tls";
+ accountParams.ServerAddr = serverAddr;
+
+ accountParams.RegisterEnabled = true;
+
+ Account account = Core.CreateAccount(accountParams);
+ Core.AddAccount(account);
+ Core.DefaultAccount = account;
+ }
+
+ public void LogOut()
+ {
+ Account account = Core.DefaultAccount;
+ if (account != null)
+ {
+ AccountParams accountParams = account.Params.Clone();
+ accountParams.RegisterEnabled = false;
+ account.Params = accountParams;
+ }
+ }
+
+ public void ClearCoreAfterLogOut()
+ {
+ Core.ClearAllAuthInfo();
+ Core.ClearAccounts();
+ }
+
+ ///
+ /// Mute/Unmute your microphone.
+ /// Set MicEnabled=false on the Core mute your microphone globally.
+ ///
+ public bool MicEnabledSwitch()
+ {
+ // The following toggles the microphone, disabling completely / enabling the sound capture from the device microphone
+ return Core.MicEnabled = !Core.MicEnabled;
+ }
+
+ ///
+ /// Enable/Disable the speaker sound.
+ /// Set SpeakerMuted=true on a Call object to disable the sound of this call.
+ ///
+ public bool SpeakerMutedSwitch()
+ {
+ return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
+ }
+
+ public async Task OpenMicrophonePopup()
+ {
+ AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
+ CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
+ AudioGraph audioGraph = result.Graph;
+
+ CreateAudioDeviceInputNodeResult resultNode = await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media);
+ AudioDeviceInputNode deviceInputNode = resultNode.DeviceInputNode;
+
+ deviceInputNode.Dispose();
+ audioGraph.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Views/CallsPage.xaml b/uwp/cs/02_IncomingCall/Views/CallsPage.xaml
new file mode 100644
index 0000000..c74bda8
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Views/CallsPage.xaml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Views/CallsPage.xaml.cs b/uwp/cs/02_IncomingCall/Views/CallsPage.xaml.cs
new file mode 100644
index 0000000..aa0ea92
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Views/CallsPage.xaml.cs
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _02_IncomingCall.Service;
+using Linphone;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _02_IncomingCall.Views
+{
+ public sealed partial class CallsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private Call IncommingCall;
+
+ public CallsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ CoreService.RemoveOnCallStateChangedDelegate(OnCallStateChanged);
+ base.OnNavigatedFrom(e);
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ // We can find current AuthInfo in the DefaultProxyConfig, we use this to say "Hello".
+ HelloText.Text += CoreService.Core.DefaultProxyConfig.FindAuthInfo().Username;
+
+ // On each stage of a call we want to update our GUI.
+ // The same way we did it for OnAccountRegistrationStateChanged we can register
+ // a delegate called every time the state of a call changed.
+ // Watch this.OnCallStateChanged for more details
+ CoreService.AddOnCallStateChangedDelegate(OnCallStateChanged);
+
+ if (CoreService.Core.CurrentCall != null)
+ {
+ OnCallStateChanged(CoreService.Core, CoreService.Core.CurrentCall, CoreService.Core.CurrentCall.State, null);
+ }
+ }
+
+ ///
+ /// Method called when the "Hang out" button is clicked.
+ ///
+ private void HangOutClick(object sender, RoutedEventArgs e)
+ {
+ // Simply call TerminateAllCalls to hang out.
+ // You could also do something like CoreService.Core.CurrentCall?.Terminate();
+ CoreService.Core.TerminateAllCalls();
+ }
+
+ ///
+ /// Method called when the "Switch on/off" button is clicked.
+ /// Watch CoreService.SpeakerMutedSwitch for more info.
+ ///
+ private void SoundClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.SpeakerMutedSwitch())
+ {
+ Sound.Content = "Switch on Sound";
+ }
+ else
+ {
+ Sound.Content = "Switch off Sound";
+ }
+ }
+
+ ///
+ /// Method to mute/unmute your microphone.
+ /// Watch CoreService.MicEnabledSwitch for more info.
+ ///
+ private void MicClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.MicEnabledSwitch())
+ {
+ Mic.Content = "Mute";
+ }
+ else
+ {
+ Mic.Content = "Unmute";
+ }
+ }
+
+ ///
+ /// We registered this method to be called every time the call state is updated.
+ /// You can find all the different call states in the CallState class.
+ /// Every time this method is called we update a TextBlock so you can follow the call state visually.
+ ///
+ private void OnCallStateChanged(Core core, Call call, CallState state, string message)
+ {
+ CallText.Text = "Your call state is : " + state.ToString();
+ switch (state)
+ {
+ case CallState.IncomingReceived:
+ // When you receive a call the CallState is incoming receive. By default you can only have one current call,
+ // so if a call is a progress or one is already ringing the second remote call will be decline with the reason
+ // "Busy". If you want to implement a multi call app you can increase Core.MaxCalls.
+ // Here we store the incoming call reference so we can accept or decline the call on user input, see AnswerClick
+ // and DeclineClick.
+ IncommingCall = call;
+ // And we update the GUI to notify the user of the incoming call.
+ IncomingCallStackPanel.Visibility = Visibility.Visible;
+ IncommingCallText.Text = " " + IncommingCall.RemoteAddress.AsString();
+
+ break;
+
+ case CallState.StreamsRunning:
+ // The StreamsRunning state is the default one during a call.
+ CallInProgressGuiUpdates();
+ break;
+
+ case CallState.Error:
+ case CallState.End:
+ case CallState.Released:
+ // By default after 30 seconds of ringing without accept or decline a call is
+ // automatically ended.
+ IncommingCall = null;
+ EndingCallGuiUpdates();
+
+ break;
+ }
+ }
+
+ ///
+ /// Method called when the "Answer" button is clicked
+ ///
+ private async void AnswerClick(object sender, RoutedEventArgs e)
+ {
+ if (IncommingCall != null)
+ {
+ // We call this method to pop the microphone permission window.
+ // If the permission was already granted for this app, no pop up
+ // appears.
+ await CoreService.OpenMicrophonePopup();
+
+ // To accept a call only use the Accept() method on the call object.
+ // If we wanted, we could create a CallParams object and answer using this object to make changes to the call configuration.
+ IncommingCall.Accept();
+ IncommingCall = null;
+ }
+ }
+
+ ///
+ /// Method called when the "Decline" button is clicked.
+ ///
+ private void DeclineClick(object sender, RoutedEventArgs e)
+ {
+ if (IncommingCall != null)
+ {
+ // You have do give a Reason to decline a call. This info is sent to the remote.
+ // See Linphone.Reason to see the full list.
+ IncommingCall.Decline(Reason.Declined);
+ IncommingCall = null;
+ }
+ }
+
+ ///
+ /// Update GUI when there is no more current call
+ ///
+ private void EndingCallGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ HangOut.IsEnabled = false;
+ Sound.IsEnabled = false;
+ Mic.IsEnabled = false;
+ Mic.Content = "Mute";
+ Sound.Content = "Switch off Sound";
+ }
+
+ ///
+ /// Update GUI when a call is running
+ ///
+ private void CallInProgressGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ HangOut.IsEnabled = true;
+ Sound.IsEnabled = true;
+ Mic.IsEnabled = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Views/LoginPage.xaml b/uwp/cs/02_IncomingCall/Views/LoginPage.xaml
new file mode 100644
index 0000000..0fd3feb
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Views/LoginPage.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Views/LoginPage.xaml.cs b/uwp/cs/02_IncomingCall/Views/LoginPage.xaml.cs
new file mode 100644
index 0000000..8a8b617
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Views/LoginPage.xaml.cs
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _02_IncomingCall.Service;
+using Linphone;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _02_IncomingCall.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class LoginPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public LoginPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ CoreService.AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ }
+
+ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+ {
+ CoreService.RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ base.OnNavigatingFrom(e);
+ }
+
+ ///
+ /// Called when you click on the "Login" button.
+ ///
+ private void LogInClick(object sender, RoutedEventArgs e)
+ {
+ if (LogIn.IsEnabled)
+ {
+ LogIn.IsEnabled = false;
+
+ CoreService.LogIn(Identity.Text, Password.Password);
+ }
+ }
+
+ ///
+ /// Called when a key is pressed and released on the login page.
+ /// If you pressed "Enter", simulate a login click.
+ ///
+ private void GridKeyUp(object sender, KeyRoutedEventArgs e)
+ {
+ if (VirtualKey.Enter.Equals(e.Key))
+ {
+ LogInClick(null, null);
+ }
+ }
+
+ ///
+ /// This method is called every time the RegistrationState is updated by background core's actions.
+ /// In this example we use this to update the GUI.
+ ///
+ private void OnAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, string message)
+ {
+ RegistrationText.Text = "Your registration state is : " + state.ToString();
+ switch (state)
+ {
+ case RegistrationState.Cleared:
+ case RegistrationState.None:
+ CoreService.ClearCoreAfterLogOut();
+ LogIn.IsEnabled = true;
+ break;
+
+ case RegistrationState.Ok:
+ LogIn.IsEnabled = false;
+ this.Frame.Navigate(typeof(NavigationRoot));
+ break;
+
+ case RegistrationState.Progress:
+ LogIn.IsEnabled = false;
+ break;
+
+ case RegistrationState.Failed:
+ LogIn.IsEnabled = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Views/NavigationRoot.xaml b/uwp/cs/02_IncomingCall/Views/NavigationRoot.xaml
new file mode 100644
index 0000000..9cafe3a
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Views/NavigationRoot.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/02_IncomingCall/Views/NavigationRoot.xaml.cs b/uwp/cs/02_IncomingCall/Views/NavigationRoot.xaml.cs
new file mode 100644
index 0000000..f5dda88
--- /dev/null
+++ b/uwp/cs/02_IncomingCall/Views/NavigationRoot.xaml.cs
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _02_IncomingCall.Service;
+using System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _02_IncomingCall.Views
+{
+ public sealed partial class NavigationRoot : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+ private bool hasLoadedPreviously;
+
+ public NavigationRoot()
+ {
+ this.InitializeComponent();
+ }
+
+ private void Page_Loaded(object sender, RoutedEventArgs e)
+ {
+ // Only do an inital navigate the first time the page loads
+ // when we switch out of compactoverloadmode this will fire but we don't want to navigate because
+ // there is already a page loaded
+ if (!hasLoadedPreviously)
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ hasLoadedPreviously = true;
+ }
+ }
+
+ private void AppNavFrame_Navigated(object sender, NavigationEventArgs e)
+ {
+ switch (e.SourcePageType)
+ {
+ case Type c when e.SourcePageType == typeof(CallsPage):
+ ((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
+ break;
+ }
+ }
+
+ private async void Navview_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
+ {
+ if (args.IsSettingsInvoked)
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "No settings",
+ Content = "There is no settings in this little app",
+ CloseButtonText = "OK"
+ };
+
+ ContentDialogResult result = await noSettingsDialog.ShowAsync();
+ return;
+ }
+
+ string invokedItemValue = args.InvokedItem as string;
+ if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ }
+ }
+
+ private void SignOut_Tapped(object sender, TappedRoutedEventArgs e)
+ {
+ DisplaySignOutDialog();
+ }
+
+ private async void DisplaySignOutDialog()
+ {
+ ContentDialog signOutDialog = new ContentDialog
+ {
+ Title = "Sign out ?",
+ Content = "All your current calls and actions will be canceled, are you sure to continue ?",
+ PrimaryButtonText = "Sign out",
+ CloseButtonText = "Cancel"
+ };
+
+ ContentDialogResult result = await signOutDialog.ShowAsync();
+
+ if (result == ContentDialogResult.Primary)
+ {
+ CoreService.Core.TerminateAllCalls();
+ CoreService.LogOut();
+
+ this.Frame.Navigate(typeof(LoginPage));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/03_OutgoingCall.csproj b/uwp/cs/03_OutgoingCall/03_OutgoingCall.csproj
new file mode 100644
index 0000000..ef9e3f1
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/03_OutgoingCall.csproj
@@ -0,0 +1,193 @@
+
+
+
+
+ Debug
+ x86
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}
+ AppContainerExe
+ Properties
+ _03_OutgoingCall
+ 03_OutgoingCall
+ en-US
+ UAP
+ 10.0.19041.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ true
+ false
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+ App.xaml
+
+
+
+ CallsPage.xaml
+
+
+
+ LoginPage.xaml
+
+
+
+ NavigationRoot.xaml
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ 2.1.13
+
+
+ 5.1.0-alpha.56
+
+
+ 6.2.11
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/App.xaml b/uwp/cs/03_OutgoingCall/App.xaml
new file mode 100644
index 0000000..f901e1a
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/App.xaml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/App.xaml.cs b/uwp/cs/03_OutgoingCall/App.xaml.cs
new file mode 100644
index 0000000..d76e432
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/App.xaml.cs
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _03_OutgoingCall.Service;
+using _03_OutgoingCall.Views;
+using Linphone;
+using System;
+using System.Diagnostics;
+using System.Text;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _03_OutgoingCall
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Start Linphone
+ LoggingService.Instance.LogLevel = LogLevel.Debug;
+ LoggingService.Instance.Listener.OnLogMessageWritten = OnLog;
+
+ CoreService.CoreStart(Windows.ApplicationModel.Core.CoreApplication.GetCurrentView().CoreWindow.Dispatcher);
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ if (e.PrelaunchActivated == false)
+ {
+ if (rootFrame.Content == null)
+ {
+ // When the navigation stack isn't restored navigate to the first page,
+ // configuring the new page by passing required information as a navigation
+ // parameter
+ rootFrame.Navigate(typeof(LoginPage), e.Arguments);
+ }
+ // Ensure the current window is active
+ Window.Current.Activate();
+ }
+ }
+
+ private void OnLog(LoggingService logService, string domain, LogLevel lev, string message)
+ {
+ StringBuilder builder = new StringBuilder();
+ _ = builder.Append("Linphone-[").Append(lev.ToString()).Append("](").Append(domain).Append(")").Append(message);
+ Debug.WriteLine(builder.ToString());
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Assets/LockScreenLogo.scale-200.png b/uwp/cs/03_OutgoingCall/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/uwp/cs/03_OutgoingCall/Assets/LockScreenLogo.scale-200.png differ
diff --git a/uwp/cs/03_OutgoingCall/Assets/SplashScreen.scale-200.png b/uwp/cs/03_OutgoingCall/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/uwp/cs/03_OutgoingCall/Assets/SplashScreen.scale-200.png differ
diff --git a/uwp/cs/03_OutgoingCall/Assets/Square150x150Logo.scale-200.png b/uwp/cs/03_OutgoingCall/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/uwp/cs/03_OutgoingCall/Assets/Square150x150Logo.scale-200.png differ
diff --git a/uwp/cs/03_OutgoingCall/Assets/Square44x44Logo.scale-200.png b/uwp/cs/03_OutgoingCall/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/uwp/cs/03_OutgoingCall/Assets/Square44x44Logo.scale-200.png differ
diff --git a/uwp/cs/03_OutgoingCall/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/uwp/cs/03_OutgoingCall/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/uwp/cs/03_OutgoingCall/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/uwp/cs/03_OutgoingCall/Assets/StoreLogo.png b/uwp/cs/03_OutgoingCall/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/uwp/cs/03_OutgoingCall/Assets/StoreLogo.png differ
diff --git a/uwp/cs/03_OutgoingCall/Assets/Wide310x150Logo.scale-200.png b/uwp/cs/03_OutgoingCall/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/uwp/cs/03_OutgoingCall/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/uwp/cs/03_OutgoingCall/Package.appxmanifest b/uwp/cs/03_OutgoingCall/Package.appxmanifest
new file mode 100644
index 0000000..93da7cb
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Package.appxmanifest
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+ 03_OutgoingCall
+ Anthony
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Properties/AssemblyInfo.cs b/uwp/cs/03_OutgoingCall/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b5311f5
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("03_OutgoingCall")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("03_OutgoingCall")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Properties/Default.rd.xml b/uwp/cs/03_OutgoingCall/Properties/Default.rd.xml
new file mode 100644
index 0000000..76edee8
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Properties/Default.rd.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Readme.md b/uwp/cs/03_OutgoingCall/Readme.md
new file mode 100644
index 0000000..de9b74c
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Readme.md
@@ -0,0 +1,23 @@
+Linphone X UWP tutorial 03_OutgoingCall
+========================================
+
+This time we are going to make our first video calls.
+
+New/updated files :
+
+```
+03_OutgoingCall
+│ Package.appxmanifest : For this step we added a new capability : Webcam.
+│
+└───Service :
+│ │ CoreService.cs : A singleton service which contains the Linphone.Core.
+│ │ Now updated with the ability to make video calls.
+│ │
+│ │ VideoService.cs : A singleton service which contains the code to render the video call
+│ │ on SwapChainPanel, using OpenGL.
+│
+│
+└───Views :
+│ │ CallsPage.xaml(.cs) : This is the page where you can make calls.
+│ │ This is where you will find the new Linphone's uses.
+```
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Service/CoreService.cs b/uwp/cs/03_OutgoingCall/Service/CoreService.cs
new file mode 100644
index 0000000..a119b86
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Service/CoreService.cs
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Media.Audio;
+using Windows.Media.Capture;
+using Windows.Storage;
+using Windows.UI.Core;
+using static Linphone.CoreListener;
+
+namespace _03_OutgoingCall.Service
+{
+ internal class CoreService
+ {
+ private Timer Timer;
+
+ private static readonly CoreService instance = new CoreService();
+
+ public static CoreService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private Core core;
+
+ public Core Core
+ {
+ get
+ {
+ if (core == null)
+ {
+ Factory factory = Factory.Instance;
+
+ string assetsPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share");
+ factory.TopResourcesDir = assetsPath;
+ factory.DataResourcesDir = assetsPath;
+ factory.SoundResourcesDir = Path.Combine(assetsPath, "sounds", "linphone");
+ factory.RingResourcesDir = Path.Combine(factory.SoundResourcesDir, "rings");
+ factory.ImageResourcesDir = Path.Combine(assetsPath, "images");
+ factory.MspluginsDir = ".";
+
+ core = factory.CreateCore("", "", IntPtr.Zero);
+
+ core.AudioPort = 7666;
+ core.VideoPort = 9666;
+
+ core.RootCa = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share", "Linphone", "rootca.pem");
+ core.UserCertificatesPath = ApplicationData.Current.LocalFolder.Path;
+
+ VideoActivationPolicy videoActivationPolicy = factory.CreateVideoActivationPolicy();
+ videoActivationPolicy.AutomaticallyAccept = true;
+ videoActivationPolicy.AutomaticallyInitiate = false;
+ core.VideoActivationPolicy = videoActivationPolicy;
+
+ if (core.VideoSupported())
+ {
+ core.VideoCaptureEnabled = true;
+ }
+ core.UsePreviewWindow(true);
+ }
+ return core;
+ }
+ }
+
+ public void CoreStart(CoreDispatcher dispatcher)
+ {
+ Core.Start();
+
+ Timer = new Timer(OnTimedEvent, dispatcher, 20, 20);
+ }
+
+ private async void OnTimedEvent(object state)
+ {
+ await ((CoreDispatcher)state).RunIdleAsync((args) =>
+ {
+ Core.Iterate();
+ });
+ }
+
+ public void AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged += myDelegate;
+ }
+
+ public void RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged -= myDelegate;
+ }
+
+ public void AddOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged += myDelegate;
+ }
+
+ public void RemoveOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged -= myDelegate;
+ }
+
+ public void LogIn(string identity, string password)
+ {
+ Address address = Factory.Instance.CreateAddress(identity);
+ AuthInfo authInfo = Factory.Instance.CreateAuthInfo(address.Username, "", password, "", "", address.Domain);
+ Core.AddAuthInfo(authInfo);
+
+ AccountParams accountParams = Core.CreateAccountParams();
+ accountParams.IdentityAddress = address;
+ string serverAddr = "sip:" + address.Domain + ";transport=tls";
+ accountParams.ServerAddr = serverAddr;
+
+ accountParams.RegisterEnabled = true;
+
+ Account account = Core.CreateAccount(accountParams);
+ Core.AddAccount(account);
+ Core.DefaultAccount = account;
+ }
+
+ public void LogOut()
+ {
+ Account account = Core.DefaultAccount;
+ if (account != null)
+ {
+ AccountParams accountParams = account.Params.Clone();
+ accountParams.RegisterEnabled = false;
+ account.Params = accountParams;
+ }
+ }
+
+ public void ClearCoreAfterLogOut()
+ {
+ Core.ClearAllAuthInfo();
+ Core.ClearAccounts();
+ }
+
+ ///
+ /// Make a call.
+ ///
+ public async void Call(string uriToCall)
+ {
+ // We call this method to pop the microphone permission window.
+ // If the permission was already granted for this app, no pop up
+ // appears.
+ await OpenMicrophonePopup();
+
+ // We create an Address object from the URI.
+ // This method can create an SIP Address from a username
+ // or phone number only.
+ Address address = Core.InterpretUrl(uriToCall);
+
+ // Initiate an outgoing call to the given destination Address.
+ Core.InviteAddress(address);
+ }
+
+ public bool MicEnabledSwitch()
+ {
+ return Core.MicEnabled = !Core.MicEnabled;
+ }
+
+ public bool SpeakerMutedSwitch()
+ {
+ return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
+ }
+
+ ///
+ /// Ask the peer of the current call to enable/disable the video call.
+ ///
+ public async Task CameraEnabledSwitchAsync()
+ {
+ // We call this method to pop up the webcam permission window.
+ // If the permission was already granted for this app, no pop up
+ // appears.
+ await OpenCameraPopup();
+
+ // Retrieving the current call
+ Call call = Core.CurrentCall;
+
+ // Core.createCallParams(call) create CallParams matching the Call parameters,
+ // here the current call. CallParams contains a variety of parameters like
+ // audio bandwidth limit, media encryption type... And if the video is enable
+ // or not.
+ CallParams param = core.CreateCallParams(call);
+
+ // Switch the current VideoEnableValue
+ bool newValue = !param.VideoEnabled;
+ param.VideoEnabled = newValue;
+
+ // Try to update the call parameters with those new CallParams.
+ // If the video switch from true to false the peer can't refuse to disable the video.
+ // If the video switch from false to true and the peer don't have videoActivationPolicy.AutomaticallyAccept = true
+ // you have to wait for him to accept the update. The Call status is "Updating" during this time.
+ call.Update(param);
+
+ return newValue;
+ }
+
+ private async Task OpenMicrophonePopup()
+ {
+ AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
+ CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
+ AudioGraph audioGraph = result.Graph;
+
+ CreateAudioDeviceInputNodeResult resultNode = await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media);
+ AudioDeviceInputNode deviceInputNode = resultNode.DeviceInputNode;
+
+ deviceInputNode.Dispose();
+ audioGraph.Dispose();
+ }
+
+ private async Task OpenCameraPopup()
+ {
+ MediaCapture mediaCapture = new Windows.Media.Capture.MediaCapture();
+ await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
+ {
+ StreamingCaptureMode = StreamingCaptureMode.Video
+ });
+ mediaCapture.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Service/VideoService.cs b/uwp/cs/03_OutgoingCall/Service/VideoService.cs
new file mode 100644
index 0000000..577a11a
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Service/VideoService.cs
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Windows.UI.Xaml.Controls;
+
+namespace _03_OutgoingCall.Service
+{
+ internal class VideoService
+ {
+ private static readonly VideoService instance = new VideoService();
+
+ public static VideoService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ ///
+ /// When you want to start the video rendering you need to link the SwapChainPanel surface to Linphone.
+ /// Core.NativePreviewWindowId for the preview surface and Core.CurrentCall.NativeVideoWindowId for the
+ /// remote webcam surface.
+ /// Simply doing this allow Linphone to render your preview and the remote camera if they are available.
+ ///
+ public void StartVideoStream(SwapChainPanel main, SwapChainPanel preview)
+ {
+ CoreService.Core.NativePreviewWindowId = preview;
+ CoreService.Core.NativeVideoWindowId = main;
+ }
+
+ public void StopVideoStream()
+ {
+ CoreService.Core.NativePreviewWindowId = null;
+ CoreService.Core.NativeVideoWindowId = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Views/CallsPage.xaml b/uwp/cs/03_OutgoingCall/Views/CallsPage.xaml
new file mode 100644
index 0000000..830f811
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Views/CallsPage.xaml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Views/CallsPage.xaml.cs b/uwp/cs/03_OutgoingCall/Views/CallsPage.xaml.cs
new file mode 100644
index 0000000..9fdb041
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Views/CallsPage.xaml.cs
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _03_OutgoingCall.Service;
+using Linphone;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _03_OutgoingCall.Views
+{
+ public sealed partial class CallsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private VideoService VideoService { get; } = VideoService.Instance;
+
+ private Call IncommingCall;
+
+ public CallsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// We just stop the video rendering when we leave the page.
+ /// Details about the video rendering implementation are in Service/VideoService.cs
+ ///
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ VideoService.StopVideoStream();
+ CoreService.RemoveOnCallStateChangedDelegate(OnCallStateChanged);
+ base.OnNavigatedFrom(e);
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ HelloText.Text += CoreService.Core.DefaultProxyConfig.FindAuthInfo().Username;
+ CoreService.AddOnCallStateChangedDelegate(OnCallStateChanged);
+
+ if (CoreService.Core.CurrentCall != null)
+ {
+ OnCallStateChanged(CoreService.Core, CoreService.Core.CurrentCall, CoreService.Core.CurrentCall.State, null);
+ }
+ }
+
+ ///
+ /// Method called when the "Call" button is clicked, see CoreService.Call
+ /// to learn how to make a call.
+ ///
+ private void CallClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Call(UriToCall.Text);
+ }
+
+ private void HangOutClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Core.TerminateAllCalls();
+ }
+
+ private void SoundClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.SpeakerMutedSwitch())
+ {
+ Sound.Content = "Switch on Sound";
+ }
+ else
+ {
+ Sound.Content = "Switch off Sound";
+ }
+ }
+
+ ///
+ /// Method to turn on/off the video call.
+ /// Watch CoreService.CameraEnabledSwitchAsync for more info.
+ ///
+ private async void CameraClick(object sender, RoutedEventArgs e)
+ {
+ await CoreService.CameraEnabledSwitchAsync();
+
+ // After CoreService.CameraEnabledSwitchAsync the Call state is "Updating".
+ // We wait for the return of the "StreamsRunning" state to update the GUI
+ // according to the final consensus between callers.
+ Camera.Content = "Waiting for accept ...";
+ Camera.IsEnabled = false;
+ }
+
+ private void MicClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.MicEnabledSwitch())
+ {
+ Mic.Content = "Mute";
+ }
+ else
+ {
+ Mic.Content = "Unmute";
+ }
+ }
+
+ private void OnCallStateChanged(Core core, Call call, CallState state, string message)
+ {
+ CallText.Text = "Your call state is : " + state.ToString();
+ switch (state)
+ {
+ case CallState.IncomingReceived:
+ IncommingCall = call;
+ IncomingCallStackPanel.Visibility = Visibility.Visible;
+ IncommingCallText.Text = " " + IncommingCall.RemoteAddress.AsString();
+
+ break;
+
+ case CallState.OutgoingInit:
+ case CallState.OutgoingProgress:
+ case CallState.OutgoingRinging:
+ // Different states you go through when you start a call and before your peer answer.
+ HangOut.IsEnabled = true;
+ break;
+
+ case CallState.StreamsRunning:
+ case CallState.UpdatedByRemote:
+ // The StreamsRunning state is the default one during a call.
+ // The UpdatedByRemote is triggered when the call's parameters are updated
+ // for example when video is asked/removed by remote.
+
+ CallInProgressGuiUpdates();
+ if (call.CurrentParams.VideoEnabled)
+ {
+ StartVideoAndUpdateGui();
+ }
+ else
+ {
+ StopVideoAndUpdateGui();
+ }
+ break;
+
+ case CallState.Error:
+ case CallState.End:
+ case CallState.Released:
+ IncommingCall = null;
+ EndingCallGuiUpdates();
+ VideoService.StopVideoStream();
+
+ break;
+ }
+ }
+
+ private void AnswerClick(object sender, RoutedEventArgs e)
+ {
+ if (IncommingCall != null)
+ {
+ IncommingCall.Accept();
+ IncommingCall = null;
+ }
+ }
+
+ private void DeclineClick(object sender, RoutedEventArgs e)
+ {
+ if (IncommingCall != null)
+ {
+ IncommingCall.Decline(Reason.Declined);
+ IncommingCall = null;
+ }
+ }
+
+ ///
+ /// Method to hide the webcam grid and stop the of the rendering remote and preview webcam.
+ /// Watch VideoService and more specifically VideoService.StopVideoStream.
+ ///
+ private void StopVideoAndUpdateGui()
+ {
+ Camera.Content = "Switch on Camera";
+ Camera.IsEnabled = true;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ VideoService.StopVideoStream();
+ }
+
+ ///
+ /// Method to show the webcam grid and start rendering remote and preview webcam.
+ /// Watch VideoService and more specifically VideoService.StartVideoStream to
+ /// understand how to start the rendering on a SwapChainPanel.
+ ///
+ private void StartVideoAndUpdateGui()
+ {
+ VideoGrid.Visibility = Visibility.Visible;
+ Camera.Content = "Switch off Camera";
+ VideoService.StartVideoStream(VideoSwapChainPanel, PreviewSwapChainPanel);
+ Camera.IsEnabled = true;
+ }
+
+ private void EndingCallGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = true;
+ HangOut.IsEnabled = false;
+ Sound.IsEnabled = false;
+ Camera.IsEnabled = false;
+ Mic.IsEnabled = false;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ Camera.Content = "Switch on Camera";
+ Mic.Content = "Mute";
+ Sound.Content = "Switch off Sound";
+ }
+
+ private void CallInProgressGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = false;
+ HangOut.IsEnabled = true;
+ Sound.IsEnabled = true;
+ Camera.IsEnabled = true;
+ Mic.IsEnabled = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Views/LoginPage.xaml b/uwp/cs/03_OutgoingCall/Views/LoginPage.xaml
new file mode 100644
index 0000000..6209a1c
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Views/LoginPage.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Views/LoginPage.xaml.cs b/uwp/cs/03_OutgoingCall/Views/LoginPage.xaml.cs
new file mode 100644
index 0000000..3b698b4
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Views/LoginPage.xaml.cs
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _03_OutgoingCall.Service;
+using Linphone;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _03_OutgoingCall.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class LoginPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public LoginPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ CoreService.AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ }
+
+ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+ {
+ CoreService.RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ base.OnNavigatingFrom(e);
+ }
+
+ ///
+ /// Called when you click on the "Login" button.
+ ///
+ private void LogInClick(object sender, RoutedEventArgs e)
+ {
+ if (LogIn.IsEnabled)
+ {
+ LogIn.IsEnabled = false;
+
+ CoreService.LogIn(Identity.Text, Password.Password);
+ }
+ }
+
+ ///
+ /// Called when a key is pressed and released on the login page.
+ /// If you pressed "Enter", simulate a login click.
+ ///
+ private void GridKeyUp(object sender, KeyRoutedEventArgs e)
+ {
+ if (VirtualKey.Enter.Equals(e.Key))
+ {
+ LogInClick(null, null);
+ }
+ }
+
+ ///
+ /// This method is called every time the RegistrationState is updated by background core's actions.
+ /// In this example we use this to update the GUI.
+ ///
+ private void OnAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, string message)
+ {
+ RegistrationText.Text = "Your registration state is : " + state.ToString();
+ switch (state)
+ {
+ case RegistrationState.Cleared:
+ case RegistrationState.None:
+ CoreService.ClearCoreAfterLogOut();
+ LogIn.IsEnabled = true;
+ break;
+
+ case RegistrationState.Ok:
+ LogIn.IsEnabled = false;
+ this.Frame.Navigate(typeof(NavigationRoot));
+ break;
+
+ case RegistrationState.Progress:
+ LogIn.IsEnabled = false;
+ break;
+
+ case RegistrationState.Failed:
+ LogIn.IsEnabled = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Views/NavigationRoot.xaml b/uwp/cs/03_OutgoingCall/Views/NavigationRoot.xaml
new file mode 100644
index 0000000..38e527e
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Views/NavigationRoot.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/03_OutgoingCall/Views/NavigationRoot.xaml.cs b/uwp/cs/03_OutgoingCall/Views/NavigationRoot.xaml.cs
new file mode 100644
index 0000000..1dd6b2c
--- /dev/null
+++ b/uwp/cs/03_OutgoingCall/Views/NavigationRoot.xaml.cs
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _03_OutgoingCall.Service;
+using System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _03_OutgoingCall.Views
+{
+ public sealed partial class NavigationRoot : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+ private bool hasLoadedPreviously;
+
+ public NavigationRoot()
+ {
+ this.InitializeComponent();
+ }
+
+ private void Page_Loaded(object sender, RoutedEventArgs e)
+ {
+ // Only do an inital navigate the first time the page loads
+ // when we switch out of compactoverloadmode this will fire but we don't want to navigate because
+ // there is already a page loaded
+ if (!hasLoadedPreviously)
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ hasLoadedPreviously = true;
+ }
+ }
+
+ private void AppNavFrame_Navigated(object sender, NavigationEventArgs e)
+ {
+ switch (e.SourcePageType)
+ {
+ case Type c when e.SourcePageType == typeof(CallsPage):
+ ((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
+ break;
+ }
+ }
+
+ private async void Navview_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
+ {
+ if (args.IsSettingsInvoked)
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "No settings",
+ Content = "There is no settings in this little app",
+ CloseButtonText = "OK"
+ };
+
+ ContentDialogResult result = await noSettingsDialog.ShowAsync();
+ return;
+ }
+
+ string invokedItemValue = args.InvokedItem as string;
+ if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ }
+ }
+
+ private void SignOut_Tapped(object sender, TappedRoutedEventArgs e)
+ {
+ DisplaySignOutDialog();
+ }
+
+ private async void DisplaySignOutDialog()
+ {
+ ContentDialog signOutDialog = new ContentDialog
+ {
+ Title = "Sign out ?",
+ Content = "All your current calls and actions will be canceled, are you sure to continue ?",
+ PrimaryButtonText = "Sign out",
+ CloseButtonText = "Cancel"
+ };
+
+ ContentDialogResult result = await signOutDialog.ShowAsync();
+
+ if (result == ContentDialogResult.Primary)
+ {
+ CoreService.Core.TerminateAllCalls();
+ CoreService.LogOut();
+
+ this.Frame.Navigate(typeof(LoginPage));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/04_BasicChat.csproj b/uwp/cs/04_BasicChat/04_BasicChat.csproj
new file mode 100644
index 0000000..6c425e2
--- /dev/null
+++ b/uwp/cs/04_BasicChat/04_BasicChat.csproj
@@ -0,0 +1,208 @@
+
+
+
+
+ Debug
+ x86
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}
+ AppContainerExe
+ Properties
+ _04_BasicChat
+ 04_BasicChat
+ en-US
+ UAP
+ 10.0.19041.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ true
+ false
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+ App.xaml
+
+
+
+
+ CallsPage.xaml
+
+
+
+ ChatPage.xaml
+
+
+ ChatsPage.xaml
+
+
+ LoginPage.xaml
+
+
+
+ NavigationRoot.xaml
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ 2.1.13
+
+
+ 5.1.0-alpha.56
+
+
+ 6.2.11
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/App.xaml b/uwp/cs/04_BasicChat/App.xaml
new file mode 100644
index 0000000..b54ebf7
--- /dev/null
+++ b/uwp/cs/04_BasicChat/App.xaml
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/App.xaml.cs b/uwp/cs/04_BasicChat/App.xaml.cs
new file mode 100644
index 0000000..5d56073
--- /dev/null
+++ b/uwp/cs/04_BasicChat/App.xaml.cs
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _04_BasicChat.Service;
+using _04_BasicChat.Views;
+using Linphone;
+using System;
+using System.Diagnostics;
+using System.Text;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _04_BasicChat
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Start Linphone
+ LoggingService.Instance.LogLevel = LogLevel.Debug;
+ LoggingService.Instance.Listener.OnLogMessageWritten = OnLog;
+
+ CoreService.CoreStart(Windows.ApplicationModel.Core.CoreApplication.GetCurrentView().CoreWindow.Dispatcher);
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ if (e.PrelaunchActivated == false)
+ {
+ if (rootFrame.Content == null)
+ {
+ // When the navigation stack isn't restored navigate to the first page,
+ // configuring the new page by passing required information as a navigation
+ // parameter
+ rootFrame.Navigate(typeof(LoginPage), e.Arguments);
+ }
+ // Ensure the current window is active
+ Window.Current.Activate();
+ }
+ }
+
+ private void OnLog(LoggingService logService, string domain, LogLevel lev, string message)
+ {
+ StringBuilder builder = new StringBuilder();
+ _ = builder.Append("Linphone-[").Append(lev.ToString()).Append("](").Append(domain).Append(")").Append(message);
+ Debug.WriteLine(builder.ToString());
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Assets/LockScreenLogo.scale-200.png b/uwp/cs/04_BasicChat/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/uwp/cs/04_BasicChat/Assets/LockScreenLogo.scale-200.png differ
diff --git a/uwp/cs/04_BasicChat/Assets/SplashScreen.scale-200.png b/uwp/cs/04_BasicChat/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/uwp/cs/04_BasicChat/Assets/SplashScreen.scale-200.png differ
diff --git a/uwp/cs/04_BasicChat/Assets/Square150x150Logo.scale-200.png b/uwp/cs/04_BasicChat/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/uwp/cs/04_BasicChat/Assets/Square150x150Logo.scale-200.png differ
diff --git a/uwp/cs/04_BasicChat/Assets/Square44x44Logo.scale-200.png b/uwp/cs/04_BasicChat/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/uwp/cs/04_BasicChat/Assets/Square44x44Logo.scale-200.png differ
diff --git a/uwp/cs/04_BasicChat/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/uwp/cs/04_BasicChat/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/uwp/cs/04_BasicChat/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/uwp/cs/04_BasicChat/Assets/StoreLogo.png b/uwp/cs/04_BasicChat/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/uwp/cs/04_BasicChat/Assets/StoreLogo.png differ
diff --git a/uwp/cs/04_BasicChat/Assets/Wide310x150Logo.scale-200.png b/uwp/cs/04_BasicChat/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/uwp/cs/04_BasicChat/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/uwp/cs/04_BasicChat/Package.appxmanifest b/uwp/cs/04_BasicChat/Package.appxmanifest
new file mode 100644
index 0000000..74b1639
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Package.appxmanifest
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+ 04_BasicChat
+ Anthony
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Properties/AssemblyInfo.cs b/uwp/cs/04_BasicChat/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b0c5370
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("04_BasicChat")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("04_BasicChat")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Properties/Default.rd.xml b/uwp/cs/04_BasicChat/Properties/Default.rd.xml
new file mode 100644
index 0000000..76edee8
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Properties/Default.rd.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Readme.md b/uwp/cs/04_BasicChat/Readme.md
new file mode 100644
index 0000000..b05037c
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Readme.md
@@ -0,0 +1,35 @@
+Linphone X UWP tutorial 04_BasicChat
+========================================
+
+Second big step in this tutorial, we can now communicate in basic chat rooms.
+
+In this part you are going to learn how to send and receive text messages over SIP using LinphoneSDK.
+For our first step with ChatRoom we are going to create only one to one basic ChatRoom (no encryption, no ephemeral),
+and for now the tutorial app will only support text message.
+
+Don't forget to install those NuGet packages :
+ - LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
+ - Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
+ - ANGLE.WindowsStore (for video rendering, version 2.1.13 recommended)
+
+New/Updated files :
+
+```
+04_BasicChat
+└───Service :
+│ │ CoreService.cs : A singleton service which contains the Linphone.Core.
+│ │ We added some code to create new chat rooms here.
+│ │
+│ │ NavigationService.cs : A small service used to keeps reference to current pages displayed.
+│
+└───Views :
+│ │
+│ │ ChatPage.xaml(.cs) : This is the frame displayed when you select a chat room.
+│ │ For now it's a simple page where you can send message and see your
+│ │ conversation history.
+│ │
+│ │ ChatsPage.xaml(.cs) : In this page we list all the existing chat rooms. If you select
+│ │ one of them a ChatPage is render. You can also create new ChatRoom here.
+│ │
+│ │ NavigationRoot.xaml(.cs) : The navigation page, you can now navigate the ChatsPage !
+```
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Service/CoreService.cs b/uwp/cs/04_BasicChat/Service/CoreService.cs
new file mode 100644
index 0000000..dc5d3f4
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Service/CoreService.cs
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Media.Audio;
+using Windows.Media.Capture;
+using Windows.Storage;
+using Windows.UI.Core;
+using static Linphone.CoreListener;
+
+namespace _04_BasicChat.Service
+{
+ internal class CoreService
+ {
+ private Timer Timer;
+
+ private static readonly CoreService instance = new CoreService();
+
+ public static CoreService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private Core core;
+
+ public Core Core
+ {
+ get
+ {
+ if (core == null)
+ {
+ Factory factory = Factory.Instance;
+
+ string assetsPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share");
+ factory.TopResourcesDir = assetsPath;
+ factory.DataResourcesDir = assetsPath;
+ factory.SoundResourcesDir = Path.Combine(assetsPath, "sounds", "linphone");
+ factory.RingResourcesDir = Path.Combine(factory.SoundResourcesDir, "rings");
+ factory.ImageResourcesDir = Path.Combine(assetsPath, "images");
+ factory.MspluginsDir = ".";
+
+ core = factory.CreateCore("", "", IntPtr.Zero);
+
+ core.AudioPort = 7666;
+ core.VideoPort = 9666;
+
+ core.RootCa = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share", "Linphone", "rootca.pem");
+ core.UserCertificatesPath = ApplicationData.Current.LocalFolder.Path;
+
+ VideoActivationPolicy videoActivationPolicy = factory.CreateVideoActivationPolicy();
+ videoActivationPolicy.AutomaticallyAccept = true;
+ videoActivationPolicy.AutomaticallyInitiate = false;
+ core.VideoActivationPolicy = videoActivationPolicy;
+
+ if (core.VideoSupported())
+ {
+ core.VideoDisplayFilter = "MSOGL";
+ core.VideoCaptureEnabled = true;
+ }
+ core.UsePreviewWindow(true);
+ }
+ return core;
+ }
+ }
+
+ public void CoreStart(CoreDispatcher dispatcher)
+ {
+ Core.Start();
+
+ Timer = new Timer(OnTimedEvent, dispatcher, 20, 20);
+ }
+
+ private async void OnTimedEvent(object state)
+ {
+ await ((CoreDispatcher)state).RunIdleAsync((args) =>
+ {
+ Core.Iterate();
+ });
+ }
+
+ public void AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged += myDelegate;
+ }
+
+ public void RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged -= myDelegate;
+ }
+
+ public void AddOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged += myDelegate;
+ }
+
+ public void RemoveOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged -= myDelegate;
+ }
+
+ ///
+ /// Used to add a delegate for the OnMessageReceived callback this callback is triggered every time
+ /// a message is received in ANY ChatRoom.
+ ///
+ public void AddOnOnMessageReceivedDelegate(OnMessageReceivedDelegate myDelegate)
+ {
+ Core.Listener.OnMessageReceived += myDelegate;
+ }
+
+ public void RemoveOnOnMessageReceivedDelegate(OnMessageReceivedDelegate myDelegate)
+ {
+ Core.Listener.OnMessageReceived -= myDelegate;
+ }
+
+ ///
+ /// Used to add a delegate for the OnMessageReceived callback this callback is triggered every time
+ /// a message is sent in ANY ChatRoom.
+ ///
+ public void AddOnMessageSentDelegate(OnMessageSentDelegate myDelegate)
+ {
+ Core.Listener.OnMessageSent += myDelegate;
+ }
+
+ public void RemoveOnMessageSentDelegate(OnMessageSentDelegate myDelegate)
+ {
+ Core.Listener.OnMessageSent -= myDelegate;
+ }
+
+ public void LogIn(string identity, string password)
+ {
+ Address address = Factory.Instance.CreateAddress(identity);
+ AuthInfo authInfo = Factory.Instance.CreateAuthInfo(address.Username, "", password, "", "", address.Domain);
+ Core.AddAuthInfo(authInfo);
+
+ AccountParams accountParams = Core.CreateAccountParams();
+ accountParams.IdentityAddress = address;
+ string serverAddr = "sip:" + address.Domain + ";transport=tls";
+ accountParams.ServerAddr = serverAddr;
+
+ accountParams.RegisterEnabled = true;
+
+ Account account = Core.CreateAccount(accountParams);
+ Core.AddAccount(account);
+ Core.DefaultAccount = account;
+ }
+
+ public void LogOut()
+ {
+ Account account = Core.DefaultAccount;
+ if (account != null)
+ {
+ AccountParams accountParams = account.Params.Clone();
+ accountParams.RegisterEnabled = false;
+ account.Params = accountParams;
+ }
+ }
+
+ public void ClearCoreAfterLogOut()
+ {
+ Core.ClearAllAuthInfo();
+ Core.ClearAccounts();
+ }
+
+ public async void Call(string uriToCall)
+ {
+ await OpenMicrophonePopup();
+
+ Address address = Core.InterpretUrl(uriToCall);
+ Core.InviteAddress(address);
+ }
+
+ public bool MicEnabledSwitch()
+ {
+ return Core.MicEnabled = !Core.MicEnabled;
+ }
+
+ public bool SpeakerMutedSwitch()
+ {
+ return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
+ }
+
+ public async Task CameraEnabledSwitchAsync()
+ {
+ await OpenCameraPopup();
+
+ Call call = Core.CurrentCall;
+ CallParams param = core.CreateCallParams(call);
+ bool newValue = !param.VideoEnabled;
+ param.VideoEnabled = newValue;
+ call.Update(param);
+
+ return newValue;
+ }
+
+ ///
+ /// Method to create a one to one basic ChatRoom from a string sip address.
+ ///
+ public ChatRoom CreateOrGetChatRoom(string sipAddress)
+ {
+ // Construct an Address object from the string parameter if possible.
+ Address remoteAddress = Core.InterpretUrl(sipAddress);
+
+ // We get our current local Address.
+ Address localAdress = Core.DefaultProxyConfig.IdentityAddress;
+
+ // You need to create a ChatRoomParams object to configure
+ // your future ChatRoom, always use Core.CreateDefaultChatRoomParams().
+ // By default the default parameters are the ones we set just after, but we
+ // set them anyway to explain them.
+ ChatRoomParams chatRoomParams = Core.CreateDefaultChatRoomParams();
+
+ // Set the type of SIP server you want to use, if you are
+ // using a basic SIP backend you can't enable encryption or
+ // group chat.
+ chatRoomParams.Backend = ChatRoomBackend.Basic;
+ // Choose if you want to enable real time text.
+ chatRoomParams.RttEnabled = false;
+
+ // You can choose to enable encryption and choose the type of encryption
+ // want. We explain in a further step how to use LIME (Linphone
+ // Instant Message Encryption) with FlexisipChat backend.
+ chatRoomParams.EncryptionEnabled = false;
+ chatRoomParams.EncryptionBackend = ChatRoomEncryptionBackend.None;
+
+ // Enable this if you want to create a group ChatRoom. We explain in
+ // a further step how to handle group chat with FlexisipChat backend.
+ chatRoomParams.GroupEnabled = false;
+
+ // To create a ChatRoom always use :
+ // Core.CreateChatRoom(ChatRoomParams parameters, Address localAddr, IEnumerable participants);
+ // If all the parameters match an existing ChatRoom of yours,
+ // it is returned instead of creating a new one.
+ return Core.CreateChatRoom(chatRoomParams, localAdress, new[] { remoteAddress });
+ }
+
+ private async Task OpenMicrophonePopup()
+ {
+ AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
+ CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
+ AudioGraph audioGraph = result.Graph;
+
+ CreateAudioDeviceInputNodeResult resultNode = await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media);
+ AudioDeviceInputNode deviceInputNode = resultNode.DeviceInputNode;
+
+ deviceInputNode.Dispose();
+ audioGraph.Dispose();
+ }
+
+ private async Task OpenCameraPopup()
+ {
+ MediaCapture mediaCapture = new Windows.Media.Capture.MediaCapture();
+ await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
+ {
+ StreamingCaptureMode = StreamingCaptureMode.Video
+ });
+ mediaCapture.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Service/NavigationService.cs b/uwp/cs/04_BasicChat/Service/NavigationService.cs
new file mode 100644
index 0000000..3271972
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Service/NavigationService.cs
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _04_BasicChat.Views;
+
+namespace _04_BasicChat.Service
+{
+ internal class NavigationService
+ {
+ private static readonly NavigationService instance = new NavigationService();
+
+ public static NavigationService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ public NavigationRoot CurrentNavigationRoot { get; set; }
+
+ public ChatsPage CurrentChatspage { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Service/VideoService.cs b/uwp/cs/04_BasicChat/Service/VideoService.cs
new file mode 100644
index 0000000..f518e50
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Service/VideoService.cs
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Windows.UI.Xaml.Controls;
+
+namespace _04_BasicChat.Service
+{
+ internal class VideoService
+ {
+ private static readonly VideoService instance = new VideoService();
+
+ public static VideoService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public void StartVideoStream(SwapChainPanel main, SwapChainPanel preview)
+ {
+ CoreService.Core.NativePreviewWindowId = preview;
+ CoreService.Core.NativeVideoWindowId = main;
+ }
+
+ public void StopVideoStream()
+ {
+ CoreService.Core.NativePreviewWindowId = null;
+ CoreService.Core.NativeVideoWindowId = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/CallsPage.xaml b/uwp/cs/04_BasicChat/Views/CallsPage.xaml
new file mode 100644
index 0000000..11400f3
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/CallsPage.xaml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/CallsPage.xaml.cs b/uwp/cs/04_BasicChat/Views/CallsPage.xaml.cs
new file mode 100644
index 0000000..cf2b9b9
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/CallsPage.xaml.cs
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _04_BasicChat.Service;
+using Linphone;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _04_BasicChat.Views
+{
+ public sealed partial class CallsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private VideoService VideoService { get; } = VideoService.Instance;
+
+ private Call IncommingCall;
+
+ public CallsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ VideoService.StopVideoStream();
+ CoreService.RemoveOnCallStateChangedDelegate(OnCallStateChanged);
+ base.OnNavigatedFrom(e);
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ HelloText.Text += CoreService.Core.DefaultProxyConfig.FindAuthInfo().Username;
+ CoreService.AddOnCallStateChangedDelegate(OnCallStateChanged);
+
+ if (CoreService.Core.CurrentCall != null)
+ {
+ OnCallStateChanged(CoreService.Core, CoreService.Core.CurrentCall, CoreService.Core.CurrentCall.State, null);
+ }
+ }
+
+ private void CallClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Call(UriToCall.Text);
+ }
+
+ private void HangOutClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Core.TerminateAllCalls();
+ }
+
+ private void SoundClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.SpeakerMutedSwitch())
+ {
+ Sound.Content = "Switch on Sound";
+ }
+ else
+ {
+ Sound.Content = "Switch off Sound";
+ }
+ }
+
+ private async void CameraClick(object sender, RoutedEventArgs e)
+ {
+ await CoreService.CameraEnabledSwitchAsync();
+ Camera.Content = "Waiting for accept ...";
+ Camera.IsEnabled = false;
+ }
+
+ private void MicClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.MicEnabledSwitch())
+ {
+ Mic.Content = "Mute";
+ }
+ else
+ {
+ Mic.Content = "Unmute";
+ }
+ }
+
+ private void AnswerClick(object sender, RoutedEventArgs e)
+ {
+ IncommingCall.Accept();
+ IncommingCall = null;
+ }
+
+ private void DeclineClick(object sender, RoutedEventArgs e)
+ {
+ if (IncommingCall != null)
+ {
+ IncommingCall.Decline(Reason.Declined);
+ IncommingCall = null;
+ }
+ }
+
+ private void OnCallStateChanged(Core core, Call call, CallState state, string message)
+ {
+ CallText.Text = "Your call state is : " + state.ToString();
+ switch (state)
+ {
+ case CallState.IncomingReceived:
+
+ IncommingCall = call;
+ IncomingCallStackPanel.Visibility = Visibility.Visible;
+ IncommingCallText.Text = " " + call.RemoteAddress.AsString();
+ break;
+
+ case CallState.OutgoingInit:
+ case CallState.OutgoingProgress:
+ case CallState.OutgoingRinging:
+
+ HangOut.IsEnabled = true;
+ break;
+
+ case CallState.StreamsRunning:
+ case CallState.UpdatedByRemote:
+
+ CallInProgressGuiUpdates();
+ if (call.CurrentParams.VideoEnabled)
+ {
+ StartVideoAndUpdateGui();
+ }
+ else
+ {
+ StopVideoAndUpdateGui();
+ }
+ break;
+
+ case CallState.Error:
+ case CallState.End:
+ case CallState.Released:
+
+ IncommingCall = null;
+ EndingCallGuiUpdates();
+ VideoService.StopVideoStream();
+ break;
+ }
+ }
+
+ private void StopVideoAndUpdateGui()
+ {
+ Camera.Content = "Switch on Camera";
+ Camera.IsEnabled = true;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ VideoService.StopVideoStream();
+ }
+
+ private void StartVideoAndUpdateGui()
+ {
+ VideoGrid.Visibility = Visibility.Visible;
+ Camera.Content = "Switch off Camera";
+ VideoService.StartVideoStream(VideoSwapChainPanel, PreviewSwapChainPanel);
+ Camera.IsEnabled = true;
+ }
+
+ private void EndingCallGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = true;
+ HangOut.IsEnabled = false;
+ Sound.IsEnabled = false;
+ Camera.IsEnabled = false;
+ Mic.IsEnabled = false;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ Camera.Content = "Switch on Camera";
+ Mic.Content = "Mute";
+ Sound.Content = "Switch off Sound";
+ }
+
+ private void CallInProgressGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = false;
+ HangOut.IsEnabled = true;
+ Sound.IsEnabled = true;
+ Camera.IsEnabled = true;
+ Mic.IsEnabled = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/ChatPage.xaml b/uwp/cs/04_BasicChat/Views/ChatPage.xaml
new file mode 100644
index 0000000..d9659ff
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/ChatPage.xaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/ChatPage.xaml.cs b/uwp/cs/04_BasicChat/Views/ChatPage.xaml.cs
new file mode 100644
index 0000000..9a5f030
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/ChatPage.xaml.cs
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _04_BasicChat.Service;
+using Linphone;
+using System.Linq;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _04_BasicChat.Views
+{
+ public sealed partial class ChatPage : Page
+ {
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+
+ private ChatRoom ChatRoom;
+
+ public ChatPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ ChatRoom = ((ChatRoom)e.Parameter);
+
+ // The ChatRoom also offers to register to some callbacks.
+ // One of them is OnMessageReceived, like the one we used
+ // on the core (Core.Listener.OnMessageReceived) but this one
+ // is triggered only when the message received is part of this
+ // ChatRoom.
+ ChatRoom.Listener.OnMessageReceived += OnMessageReceived;
+
+ // The method GetHistory get all the ChatMessage you have
+ // in your local database for this ChatRoom. GetHistory(0)
+ // means everything but you can specify a max number of messages.
+ foreach (ChatMessage chatMessage in ChatRoom.GetHistory(0))
+ {
+ // See AddMessage(ChatMessage chatMessage) to see how we display messages
+ AddMessage(chatMessage);
+ }
+
+ // Mark all the messages in th ChatRoom as read, if some messages
+ // weren't, this will trigger some read notifications to the remote.
+ ChatRoom.MarkAsRead();
+
+ // Only here to update display of unread message count on parent frames.
+ // See NavigationRoot.UpdateUnreadMessageCount() to see how to get a
+ // global unread message count.
+ NavigationService.CurrentNavigationRoot.UpdateUnreadMessageCount();
+ NavigationService.CurrentChatspage.UpdateChatRooms();
+
+ // We can find all the info from the peer in the PeerAddress attribute
+ // of a ChatRoom object.
+ ChatHeaderText.Text += ChatRoom.PeerAddress.Username;
+ PeerUsername.Text += ChatRoom.PeerAddress.Username;
+ // And yours in LocalAddress.
+ YourUsername.Text += ChatRoom.LocalAddress.Username;
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ // Don't forget to unregister delegate to avoid memory leak
+ ChatRoom.Listener = null;
+ }
+
+ ///
+ /// Delegate method called every time a message is received in this chat room.
+ ///
+ private void OnMessageReceived(ChatRoom chatRoom, ChatMessage message)
+ {
+ if (ChatRoom != null)
+ {
+ AddMessage(message);
+ chatRoom.MarkAsRead();
+ }
+ }
+
+ private void AddMessage(ChatMessage chatMessage)
+ {
+ TextBlock textBlock = new TextBlock();
+
+ // You can find a lot of information on a ChatMessage object.
+ // Here we used the IsOutgoing info to choose on each side
+ // of the frame the message should be displayed.
+ if (chatMessage.IsOutgoing)
+ {
+ textBlock.HorizontalAlignment = HorizontalAlignment.Right;
+ }
+ else
+ {
+ textBlock.HorizontalAlignment = HorizontalAlignment.Left;
+ }
+
+ // You can see we take the first element of the Contents list of our ChatMessage. To keep
+ // it simple we assume that we only send simple text message, we will talk more about multipart
+ // messages and other types of messagse in the next step.
+ // For now we only handle chat messages with one content, so we can find our text in
+ // chatMessage.Contents.First().Utf8Text.
+ // We used ["" +] because if the message is a file transfer for example the Utf8Text can be null.
+ textBlock.Text = "" + chatMessage.Contents.First().Utf8Text;
+
+ MessagesList.Children.Add(textBlock);
+
+ ScrollToBottom();
+ }
+
+ private void ScrollToBottom()
+ {
+ MessagesScroll.UpdateLayout();
+ MessagesScroll.ChangeView(1, MessagesScroll.ExtentHeight, 1);
+ }
+
+ ///
+ /// Method called when the "Send" button is clicked
+ ///
+ private void OutgoingMessageButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (ChatRoom != null && OutgoingMessageText.Text != null && OutgoingMessageText.Text.Length > 0)
+ {
+ // We use the ChatRoom to create a new ChatMessage object. Here we used
+ // the method CreateMessage(string message) to create a text message.
+ ChatMessage chatMessage = ChatRoom.CreateMessage(OutgoingMessageText.Text);
+
+ // And simply call the Send() method to send the message.
+ chatMessage.Send();
+
+ AddMessage(chatMessage);
+ }
+ OutgoingMessageText.Text = "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/ChatsPage.xaml b/uwp/cs/04_BasicChat/Views/ChatsPage.xaml
new file mode 100644
index 0000000..f70af6f
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/ChatsPage.xaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/ChatsPage.xaml.cs b/uwp/cs/04_BasicChat/Views/ChatsPage.xaml.cs
new file mode 100644
index 0000000..c731698
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/ChatsPage.xaml.cs
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _04_BasicChat.Service;
+using Linphone;
+using System;
+using System.Threading.Tasks;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _04_BasicChat.Views
+{
+ public sealed partial class ChatsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+
+ public ChatsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ // We just do this so we can update the list from other pages.
+ NavigationService.CurrentChatspage = this;
+
+ // Find and update the chat rooms list, see UpdateChatRooms
+ UpdateChatRooms();
+
+ // You are now familiar with those kinds of callback register.
+ // Here we want to update the list every time a message is
+ // received (list order and unread message count, see ChatsPage.xaml)
+ // or sent (list order)
+ CoreService.AddOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
+ CoreService.AddOnMessageSentDelegate(OnMessageReceiveOrSent);
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ NavigationService.CurrentChatspage = null;
+
+ // You need to unregister delegate to allow the garbage collector to
+ // collect this instance when you navigate away.
+ CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
+ CoreService.RemoveOnMessageSentDelegate(OnMessageReceiveOrSent);
+
+ base.OnNavigatedFrom(e);
+ }
+
+ ///
+ /// Method called too update the list every time a message is received or sent.
+ ///
+ private void OnMessageReceiveOrSent(Core core, ChatRoom chatRoom, ChatMessage message) => UpdateChatRooms();
+
+ public void UpdateChatRooms()
+ {
+ ChatRoom selectedChatRoom = (ChatRoom)ChatRoomsLV.SelectedItem;
+ ChatRoomsLV.Items.Clear();
+
+ // In the ChatRooms list attribute you can find every ChatRooms linked
+ // to your user. The list is ordered by ChatRoom last activity date
+ // (most recent first).
+ // You can see in Chats.xaml that we only use the properties
+ // UnreadMessagesCount and PeerAdress to display our chat rooms.
+ // In further steps we will do more.
+ foreach (ChatRoom chatRoom in CoreService.Core.ChatRooms)
+ {
+ // Here we use the HistorySize attribute to display only
+ // ChatRooms were at least one message was exchange.
+ if (chatRoom.HistorySize > 0)
+ {
+ ChatRoomsLV.Items.Add(chatRoom);
+ if (selectedChatRoom == chatRoom)
+ {
+ ChatRoomsLV.SelectedItem = chatRoom;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Method called when any item of the chat rooms ListView is clicked.
+ ///
+ private void ChatRoomsLV_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ ChatRoomsLV.SelectedItem = e.ClickedItem;
+ ChatRoomFrame.Navigate(typeof(ChatPage), e.ClickedItem);
+ }
+
+ private async void NewChatRoom_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ string peerSipAddress = await InputTextDialogAsync("Enter peer sip address");
+ if (!String.IsNullOrWhiteSpace(peerSipAddress))
+ {
+ // We create a new ChatRoom with the address the user gave us.
+ // See CoreService.CreateOrGetChatRoom(string sipAddress) for more info
+ ChatRoom newChatRoom = CoreService.CreateOrGetChatRoom(peerSipAddress);
+
+ if (newChatRoom != null)
+ {
+ // If the ChatRoom creation succeed render/navigate to a ChatPage in the inner
+ // frame of the ChatsPage.
+ // See ChatPage.xaml.cs to understand how to get message history and how to send/receive
+ // and display new messages.
+ ChatRoomFrame.Navigate(typeof(ChatPage), newChatRoom);
+ }
+ else
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "ChatRoom creation error",
+ Content = "An error occurred during ChatRoom creation, check sip address validity and try again.",
+ CloseButtonText = "OK"
+ };
+
+ await noSettingsDialog.ShowAsync();
+ }
+ }
+ }
+
+ ///
+ /// Small utility method to display a dialog with an input text
+ ///
+ private async Task InputTextDialogAsync(string title)
+ {
+ TextBox inputTextBox = new TextBox
+ {
+ AcceptsReturn = false,
+ Height = 32
+ };
+ ContentDialog dialog = new ContentDialog
+ {
+ Content = inputTextBox,
+ Title = title,
+ IsSecondaryButtonEnabled = true,
+ PrimaryButtonText = "OK",
+ SecondaryButtonText = "Cancel"
+ };
+ if (await dialog.ShowAsync() == ContentDialogResult.Primary)
+ return inputTextBox.Text;
+ else
+ return "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/LoginPage.xaml b/uwp/cs/04_BasicChat/Views/LoginPage.xaml
new file mode 100644
index 0000000..78b4655
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/LoginPage.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/LoginPage.xaml.cs b/uwp/cs/04_BasicChat/Views/LoginPage.xaml.cs
new file mode 100644
index 0000000..3293a58
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/LoginPage.xaml.cs
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _04_BasicChat.Service;
+using Linphone;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _04_BasicChat.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class LoginPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public LoginPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ CoreService.AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ }
+
+ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+ {
+ CoreService.RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ base.OnNavigatingFrom(e);
+ }
+
+ ///
+ /// Called when you click on the "Login" button.
+ ///
+ private void LogInClick(object sender, RoutedEventArgs e)
+ {
+ if (LogIn.IsEnabled)
+ {
+ LogIn.IsEnabled = false;
+
+ CoreService.LogIn(Identity.Text, Password.Password);
+ }
+ }
+
+ ///
+ /// Called when a key is pressed and released on the login page.
+ /// If you pressed "Enter", simulate a login click.
+ ///
+ private void GridKeyUp(object sender, KeyRoutedEventArgs e)
+ {
+ if (VirtualKey.Enter.Equals(e.Key))
+ {
+ LogInClick(null, null);
+ }
+ }
+
+ ///
+ /// This method is called every time the RegistrationState is updated by background core's actions.
+ /// In this example we use this to update the GUI.
+ ///
+ private void OnAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, string message)
+ {
+ RegistrationText.Text = "Your registration state is : " + state.ToString();
+ switch (state)
+ {
+ case RegistrationState.Cleared:
+ case RegistrationState.None:
+ CoreService.ClearCoreAfterLogOut();
+ LogIn.IsEnabled = true;
+ break;
+
+ case RegistrationState.Ok:
+ LogIn.IsEnabled = false;
+ this.Frame.Navigate(typeof(NavigationRoot));
+ break;
+
+ case RegistrationState.Progress:
+ LogIn.IsEnabled = false;
+ break;
+
+ case RegistrationState.Failed:
+ LogIn.IsEnabled = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/NavigationRoot.xaml b/uwp/cs/04_BasicChat/Views/NavigationRoot.xaml
new file mode 100644
index 0000000..f6ad6c6
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/NavigationRoot.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/04_BasicChat/Views/NavigationRoot.xaml.cs b/uwp/cs/04_BasicChat/Views/NavigationRoot.xaml.cs
new file mode 100644
index 0000000..c21fe76
--- /dev/null
+++ b/uwp/cs/04_BasicChat/Views/NavigationRoot.xaml.cs
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _04_BasicChat.Service;
+using Linphone;
+using System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _04_BasicChat.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class NavigationRoot : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+ private bool hasLoadedPreviously;
+
+ public NavigationRoot()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ this.CoreService.AddOnOnMessageReceivedDelegate(OnMessageReveive);
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ this.CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReveive);
+ base.OnNavigatedFrom(e);
+ }
+
+ private void Page_Loaded(object sender, RoutedEventArgs e)
+ {
+ // Only do an inital navigate the first time the page loads
+ // when we switch out of compactoverloadmode this will fire but we don't want to navigate because
+ // there is already a page loaded
+ if (!hasLoadedPreviously)
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ UpdateUnreadMessageCount();
+ hasLoadedPreviously = true;
+ NavigationService.CurrentNavigationRoot = this;
+ }
+ }
+
+ private void AppNavFrame_Navigated(object sender, NavigationEventArgs e)
+ {
+ switch (e.SourcePageType)
+ {
+ case Type c when e.SourcePageType == typeof(CallsPage):
+ ((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
+ break;
+
+ case Type c when e.SourcePageType == typeof(ChatsPage):
+ ((NavigationViewItem)navview.MenuItems[1]).IsSelected = true;
+ break;
+ }
+ }
+
+ private async void Navview_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
+ {
+ if (args.IsSettingsInvoked)
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "No settings",
+ Content = "There is no settings in this little app",
+ CloseButtonText = "OK"
+ };
+
+ ContentDialogResult result = await noSettingsDialog.ShowAsync();
+ return;
+ }
+
+ string invokedItemValue = args.InvokedItem as string;
+ if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ }
+ else
+ {
+ AppNavFrame.Navigate(typeof(ChatsPage));
+ }
+ }
+
+ private void SignOut_Tapped(object sender, TappedRoutedEventArgs e)
+ {
+ DisplaySignOutDialog();
+ }
+
+ private async void DisplaySignOutDialog()
+ {
+ ContentDialog signOutDialog = new ContentDialog
+ {
+ Title = "Sign out ?",
+ Content = "All your current calls and actions will be canceled, are you sure to continue ?",
+ PrimaryButtonText = "Sign out",
+ CloseButtonText = "Cancel"
+ };
+
+ ContentDialogResult result = await signOutDialog.ShowAsync();
+
+ if (result == ContentDialogResult.Primary)
+ {
+ CoreService.Core.TerminateAllCalls();
+ CoreService.LogOut();
+
+ this.Frame.Navigate(typeof(LoginPage));
+ }
+ }
+
+ private void OnMessageReveive(Core core, ChatRoom chatRoom, ChatMessage message)
+ {
+ UpdateUnreadMessageCount();
+ }
+
+ public void UpdateUnreadMessageCount()
+ {
+ // The property UnreadChatMessageCountFromActiveLocals return the total
+ // number of unread messages in all the chat rooms off all connected accounts
+ // on the device. In the tutorial we only allow one account at a time, so
+ // you get the global unread message count for your account.
+ if (CoreService.Core.UnreadChatMessageCountFromActiveLocals > 0)
+ {
+ NewMessageCount.Text = "" + CoreService.Core.UnreadChatMessageCountFromActiveLocals;
+ NewMessageCountBorder.Visibility = Visibility.Visible;
+ }
+ else
+ {
+ NewMessageCountBorder.Visibility = Visibility.Collapsed;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/05_FileTransfer.csproj b/uwp/cs/05_FileTransfer/05_FileTransfer.csproj
new file mode 100644
index 0000000..10ea2df
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/05_FileTransfer.csproj
@@ -0,0 +1,215 @@
+
+
+
+
+ Debug
+ x86
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}
+ AppContainerExe
+ Properties
+ _05_FileTransfer
+ 05_FileTransfer
+ en-US
+ UAP
+ 10.0.19041.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ true
+ false
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+ App.xaml
+
+
+ MessageDisplay.xaml
+
+
+
+
+ CallsPage.xaml
+
+
+
+ ChatPage.xaml
+
+
+ ChatsPage.xaml
+
+
+ LoginPage.xaml
+
+
+
+ NavigationRoot.xaml
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ 2.1.13
+
+
+ 5.1.0-alpha.56
+
+
+ 6.2.11
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/App.xaml b/uwp/cs/05_FileTransfer/App.xaml
new file mode 100644
index 0000000..80f7dc4
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/App.xaml
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/App.xaml.cs b/uwp/cs/05_FileTransfer/App.xaml.cs
new file mode 100644
index 0000000..5795831
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/App.xaml.cs
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _05_FileTransfer.Service;
+using _05_FileTransfer.Views;
+using Linphone;
+using System;
+using System.Diagnostics;
+using System.Text;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _05_FileTransfer
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Start Linphone
+ LoggingService.Instance.LogLevel = LogLevel.Debug;
+ LoggingService.Instance.Listener.OnLogMessageWritten = OnLog;
+
+ CoreService.CoreStart(Windows.ApplicationModel.Core.CoreApplication.GetCurrentView().CoreWindow.Dispatcher);
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ if (e.PrelaunchActivated == false)
+ {
+ if (rootFrame.Content == null)
+ {
+ // When the navigation stack isn't restored navigate to the first page,
+ // configuring the new page by passing required information as a navigation
+ // parameter
+ rootFrame.Navigate(typeof(LoginPage), e.Arguments);
+ }
+ // Ensure the current window is active
+ Window.Current.Activate();
+ }
+ }
+
+ private void OnLog(LoggingService logService, string domain, LogLevel lev, string message)
+ {
+ StringBuilder builder = new StringBuilder();
+ _ = builder.Append("Linphone-[").Append(lev.ToString()).Append("](").Append(domain).Append(")").Append(message);
+ Debug.WriteLine(builder.ToString());
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Assets/LockScreenLogo.scale-200.png b/uwp/cs/05_FileTransfer/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/uwp/cs/05_FileTransfer/Assets/LockScreenLogo.scale-200.png differ
diff --git a/uwp/cs/05_FileTransfer/Assets/SplashScreen.scale-200.png b/uwp/cs/05_FileTransfer/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/uwp/cs/05_FileTransfer/Assets/SplashScreen.scale-200.png differ
diff --git a/uwp/cs/05_FileTransfer/Assets/Square150x150Logo.scale-200.png b/uwp/cs/05_FileTransfer/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/uwp/cs/05_FileTransfer/Assets/Square150x150Logo.scale-200.png differ
diff --git a/uwp/cs/05_FileTransfer/Assets/Square44x44Logo.scale-200.png b/uwp/cs/05_FileTransfer/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/uwp/cs/05_FileTransfer/Assets/Square44x44Logo.scale-200.png differ
diff --git a/uwp/cs/05_FileTransfer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/uwp/cs/05_FileTransfer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/uwp/cs/05_FileTransfer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/uwp/cs/05_FileTransfer/Assets/StoreLogo.png b/uwp/cs/05_FileTransfer/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/uwp/cs/05_FileTransfer/Assets/StoreLogo.png differ
diff --git a/uwp/cs/05_FileTransfer/Assets/Wide310x150Logo.scale-200.png b/uwp/cs/05_FileTransfer/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/uwp/cs/05_FileTransfer/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/uwp/cs/05_FileTransfer/Controls/MessageDisplay.xaml b/uwp/cs/05_FileTransfer/Controls/MessageDisplay.xaml
new file mode 100644
index 0000000..d4c9e07
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Controls/MessageDisplay.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Controls/MessageDisplay.xaml.cs b/uwp/cs/05_FileTransfer/Controls/MessageDisplay.xaml.cs
new file mode 100644
index 0000000..b498e25
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Controls/MessageDisplay.xaml.cs
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.IO;
+using System.Linq;
+using Windows.Storage;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace _05_FileTransfer.Controls
+{
+ public sealed partial class MessageDisplay : UserControl
+ {
+ private readonly ChatMessage ChatMessage;
+ private Content CurrentShownContent;
+
+ public MessageDisplay(ChatMessage message)
+ {
+ this.InitializeComponent();
+ // We link every MessageDisplay object to a ChatMessage.
+ ChatMessage = message;
+ UpdateLayoutFromMessage();
+ }
+
+ private void MessageDisplay_Loaded(object sender, RoutedEventArgs e)
+ {
+ // Like other Linphone objects you can register to a variety
+ // of callbacks on a ChatMessage object (see ChatMessage.Listener for the list).
+ // Here we want to be called when the message state is updated.
+ ChatMessage.Listener.OnMsgStateChanged += OnMessageStateChanged;
+ }
+
+ private void MessageDisplay_Unloaded(object sender, RoutedEventArgs e)
+ {
+ // Again, don't forget to unregister to avoid memory leak.
+ ChatMessage.Listener = null;
+ }
+
+ private void OnMessageStateChanged(ChatMessage message, ChatMessageState state)
+ {
+ // We display the message state. It can be really useful for the user
+ // to know if the remote received the message (state = Delivered) or
+ // if he read it (state = Displayed)
+ MessageState.Text = "The message state is : " + state;
+
+ switch (state)
+ {
+ // They're is multiple state during a file transfer (FileTransferInProgress,
+ // FileTransferDone, FileTransferError). We update the layout if the file
+ // is done downloading to replace the "Download" button by an "Open file" button.
+ case ChatMessageState.FileTransferDone:
+ UpdateLayoutFromMessage();
+ return;
+ }
+ }
+
+ private void UpdateLayoutFromMessage()
+ {
+ MessageState.Text = "The message state is : " + ChatMessage.State;
+
+ // You can find the sending date of a ChatMessage in ChatMessage.Time.
+ // The time number respect the time_t type specification.
+ ReceiveDate.Text = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(ChatMessage.Time).ToLocalTime().ToString("HH:mm");
+
+ if (ChatMessage.IsOutgoing)
+ {
+ this.HorizontalAlignment = HorizontalAlignment.Right;
+ }
+ else
+ {
+ this.HorizontalAlignment = HorizontalAlignment.Left;
+ }
+
+ // A ChatMessage hold a list of Content object, Contents.
+ // But in a basic ChatRoom, using a basic backend, by default the multipart
+ // is disable. So in any received message there is only one Content in the list.
+ // You can enable multipart on a ChatRoom object with ChatRoom.AllowMultipart() but it
+ // can be risky. In fact if your remote doesn't support multipart and you send him
+ // a multipart message it could not work properly.
+ if (ChatMessage.Contents.Any(c => c.IsFile))
+ {
+ // If the Content object isFile it means that it is an already
+ // downloaded file, so we display the OpenFile button. See
+ // this.OpenFile_Click to understand how to find the file from
+ // the Content object.
+ TextStack.Visibility = Visibility.Collapsed;
+ FileStack.Visibility = Visibility.Visible;
+ OpenFile.Visibility = Visibility.Visible;
+ Download.Visibility = Visibility.Collapsed;
+
+ // We can do this because we don't allowMultipart and can assume
+ // they're is only one element, and ChatMessage.Contents.Any(c => c.IsFile)
+ // returned true.
+ Content content = ChatMessage.Contents.First((c) => c.IsFile);
+
+ // Here we are displaying the name and the size of the file
+ FileName.Text = content.Name;
+ FileSize.Text = content.FileSize + " bits";
+ CurrentShownContent = content;
+ }
+ else if (ChatMessage.Contents.Any(c => c.IsFileTransfer))
+ {
+ // If the Content object IsFileTransfer it means that the file is
+ // not downloaded yet, so we display the Download button. See
+ // this.Download_Click to understand how to download the file from
+ // the Content object.
+ TextStack.Visibility = Visibility.Collapsed;
+ FileStack.Visibility = Visibility.Visible;
+ Download.Visibility = Visibility.Visible;
+ OpenFile.Visibility = Visibility.Collapsed;
+
+ Content content = ChatMessage.Contents.First((c) => c.IsFileTransfer);
+
+ FileName.Text = content.Name;
+ FileSize.Text = content.FileSize + " bits";
+ CurrentShownContent = content;
+ }
+ else if (ChatMessage.Contents.Any(c => c.IsText))
+ {
+ // If the content isText we only display the text value like before
+ TextStack.Visibility = Visibility.Visible;
+ FileStack.Visibility = Visibility.Collapsed;
+
+ Content content = ChatMessage.Contents.First((c) => c.IsText);
+
+ TextMessage.Text = content.Utf8Text;
+ CurrentShownContent = null;
+ }
+ }
+
+ ///
+ /// Method called when the "Download" button is clicked
+ ///
+ private void Download_Click(object sender, RoutedEventArgs e)
+ {
+ if (CurrentShownContent != null)
+ {
+ Download.Visibility = Visibility.Collapsed;
+ FileSize.Text = "Download in progress ...";
+
+ // We create a directory where we have write rights.
+ string downloadPathFolder = ApplicationData.Current.LocalFolder.Path + @"\Downloads\";
+ Directory.CreateDirectory(downloadPathFolder);
+
+ // We set the future file path before we start the download.
+ CurrentShownContent.FilePath = downloadPathFolder + CurrentShownContent.Name;
+
+ // And we use ChatMessage.DownloadContent(Content content) with
+ // our Content object as parameter. The download is async and
+ // you can follow the file transfer with OnFileTransferProgressIndicationDelegate
+ // or simply wait the FileTransferDone state on the ChatMessage like we are
+ // doing here.
+ ChatMessage.DownloadContent(CurrentShownContent);
+ }
+ }
+
+ private async void OpenFile_Click(object sender, RoutedEventArgs e)
+ {
+ // Just get the FilePath attribute from the Content object
+ string filePath = CurrentShownContent.FilePath;
+
+ // Only keep the folder part
+ string folderPath = filePath.Substring(0, filePath.LastIndexOf("\\"));
+
+ // And launch the Windows explorer
+ await Launcher.LaunchFolderAsync(await StorageFolder.GetFolderFromPathAsync(folderPath));
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Package.appxmanifest b/uwp/cs/05_FileTransfer/Package.appxmanifest
new file mode 100644
index 0000000..4d62d86
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Package.appxmanifest
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+ 05_FileTransfer
+ Anthony
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Properties/AssemblyInfo.cs b/uwp/cs/05_FileTransfer/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..3cec8a3
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("05_FileTransfer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("05_FileTransfer")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Properties/Default.rd.xml b/uwp/cs/05_FileTransfer/Properties/Default.rd.xml
new file mode 100644
index 0000000..76edee8
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Properties/Default.rd.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Readme.md b/uwp/cs/05_FileTransfer/Readme.md
new file mode 100644
index 0000000..35a94e7
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Readme.md
@@ -0,0 +1,33 @@
+Linphone X UWP tutorial 05_FileTransfer
+========================================
+
+Learn how to send files over SIP using Linphone SDK.
+
+We added a button to send file to your peer, and we improved how messages are displayed to show
+you more information about them and allow you to download files sent by the remote end.
+Most of the new Linphone usage are in Controls/MessageDisplay.xaml(.cs) and ChatPage.xaml(.cs) but don't
+forget to set the attribute FileTransferServer on your Core ! (see Core creation in CoreService.cs)
+
+Don't forget to install those NuGet packages :
+ - LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
+ - Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
+ - ANGLE.WindowsStore (for video rendering, version 2.1.13 recommended)
+
+New/updated files :
+
+```
+05_FileTransfer
+└───Controls :
+│ │ MessageDisplay.xaml(.cs) : A user control to display chat bubbles with more
+│ │ information. Learn how to handle the different types of ChatMessage here.
+│ │
+│
+└───Service :
+│ │ CoreService.cs : A singleton service which contains the Linphone.Core.
+│ │ We setup FileTransferServer during core creation now.
+│
+└───Views :
+│ │
+│ │ ChatPage.xaml(.cs) : This is the frame displayed when you select a chat room.
+│ │ You can now send file and the message display is improved (see MessageDisplay)
+```
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Service/CoreService.cs b/uwp/cs/05_FileTransfer/Service/CoreService.cs
new file mode 100644
index 0000000..56639c2
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Service/CoreService.cs
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Media.Audio;
+using Windows.Media.Capture;
+using Windows.Storage;
+using Windows.UI.Core;
+using static Linphone.CoreListener;
+
+namespace _05_FileTransfer.Service
+{
+ internal class CoreService
+ {
+ private Timer Timer;
+
+ private static readonly CoreService instance = new CoreService();
+
+ public static CoreService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private Core core;
+
+ public Core Core
+ {
+ get
+ {
+ if (core == null)
+ {
+ Factory factory = Factory.Instance;
+
+ string assetsPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share");
+ factory.TopResourcesDir = assetsPath;
+ factory.DataResourcesDir = assetsPath;
+ factory.SoundResourcesDir = Path.Combine(assetsPath, "sounds", "linphone");
+ factory.RingResourcesDir = Path.Combine(factory.SoundResourcesDir, "rings");
+ factory.ImageResourcesDir = Path.Combine(assetsPath, "images");
+ factory.MspluginsDir = ".";
+
+ core = factory.CreateCore("", "", IntPtr.Zero);
+
+ core.AudioPort = 7666;
+ core.VideoPort = 9666;
+
+ core.RootCa = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share", "Linphone", "rootca.pem");
+ core.UserCertificatesPath = ApplicationData.Current.LocalFolder.Path;
+
+ VideoActivationPolicy videoActivationPolicy = factory.CreateVideoActivationPolicy();
+ videoActivationPolicy.AutomaticallyAccept = true;
+ videoActivationPolicy.AutomaticallyInitiate = false;
+ core.VideoActivationPolicy = videoActivationPolicy;
+
+ if (core.VideoSupported())
+ {
+ core.VideoCaptureEnabled = true;
+ }
+ core.UsePreviewWindow(true);
+
+ // You must set up your file transfer server if you want to transfer files.
+ core.FileTransferServer = "https://www.linphone.org:444/lft.php";
+ }
+ return core;
+ }
+ }
+
+ public void CoreStart(CoreDispatcher dispatcher)
+ {
+ Core.Start();
+
+ Timer = new Timer(OnTimedEvent, dispatcher, 20, 20);
+ }
+
+ private async void OnTimedEvent(object state)
+ {
+ await ((CoreDispatcher)state).RunIdleAsync((args) =>
+ {
+ Core.Iterate();
+ });
+ }
+
+ public void AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged += myDelegate;
+ }
+
+ public void RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged -= myDelegate;
+ }
+
+ public void AddOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged += myDelegate;
+ }
+
+ public void RemoveOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged -= myDelegate;
+ }
+
+ public void AddOnOnMessageReceivedDelegate(OnMessageReceivedDelegate myDelegate)
+ {
+ Core.Listener.OnMessageReceived += myDelegate;
+ }
+
+ public void RemoveOnOnMessageReceivedDelegate(OnMessageReceivedDelegate myDelegate)
+ {
+ Core.Listener.OnMessageReceived -= myDelegate;
+ }
+
+ public void AddOnMessageSentDelegate(OnMessageSentDelegate myDelegate)
+ {
+ Core.Listener.OnMessageSent += myDelegate;
+ }
+
+ public void RemoveOnMessageSentDelegate(OnMessageSentDelegate myDelegate)
+ {
+ Core.Listener.OnMessageSent -= myDelegate;
+ }
+
+ public void LogIn(string identity, string password)
+ {
+ Address address = Factory.Instance.CreateAddress(identity);
+ AuthInfo authInfo = Factory.Instance.CreateAuthInfo(address.Username, "", password, "", "", address.Domain);
+ Core.AddAuthInfo(authInfo);
+
+ AccountParams accountParams = Core.CreateAccountParams();
+ accountParams.IdentityAddress = address;
+ string serverAddr = "sip:" + address.Domain + ";transport=tls";
+ accountParams.ServerAddr = serverAddr;
+
+ accountParams.RegisterEnabled = true;
+
+ Account account = Core.CreateAccount(accountParams);
+ Core.AddAccount(account);
+ Core.DefaultAccount = account;
+ }
+
+ public void LogOut()
+ {
+ Account account = Core.DefaultAccount;
+ if (account != null)
+ {
+ AccountParams accountParams = account.Params.Clone();
+ accountParams.RegisterEnabled = false;
+ account.Params = accountParams;
+ }
+ }
+
+ public void ClearCoreAfterLogOut()
+ {
+ Core.ClearAllAuthInfo();
+ Core.ClearAccounts();
+ }
+
+ public async void Call(string uriToCall)
+ {
+ await OpenMicrophonePopup();
+
+ Address address = Core.InterpretUrl(uriToCall);
+ Core.InviteAddress(address);
+ }
+
+ public bool MicEnabledSwitch()
+ {
+ return Core.MicEnabled = !Core.MicEnabled;
+ }
+
+ public bool SpeakerMutedSwitch()
+ {
+ return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
+ }
+
+ public async Task CameraEnabledSwitchAsync()
+ {
+ await OpenCameraPopup();
+
+ Call call = Core.CurrentCall;
+ CallParams param = core.CreateCallParams(call);
+ bool newValue = !param.VideoEnabled;
+ param.VideoEnabled = newValue;
+ call.Update(param);
+
+ return newValue;
+ }
+
+ public ChatRoom CreateOrGetChatRoom(string sipAddress)
+ {
+ Address remoteAddress = Core.InterpretUrl(sipAddress);
+ Address localAdress = Core.DefaultProxyConfig.IdentityAddress;
+
+ ChatRoomParams chatRoomParams = Core.CreateDefaultChatRoomParams();
+ chatRoomParams.Backend = ChatRoomBackend.Basic;
+ chatRoomParams.EncryptionBackend = ChatRoomEncryptionBackend.None;
+ chatRoomParams.EncryptionEnabled = false;
+ chatRoomParams.GroupEnabled = false;
+ chatRoomParams.RttEnabled = false;
+
+ return Core.CreateChatRoom(chatRoomParams, localAdress, new[] { remoteAddress });
+ }
+
+ public async Task CreateContentFromFile(StorageFile file)
+ {
+ // Copy the file where the LinphoneSDK has read access.
+ StorageFile fileCopy = await file.CopyAsync(ApplicationData.Current.LocalFolder, file.Name, NameCollisionOption.ReplaceExisting);
+
+ // Always use Linphone's method and not new() to create Linphone objects.
+ Content content = Core.CreateContent();
+
+ // File Path is the only mandatory field to set.
+ content.FilePath = fileCopy.Path;
+
+ // You can set the type and subtype of your file, it help
+ // the server and receiver identifying the file (images can
+ // be directly displayed for example).
+ string[] splittedMimeType = fileCopy.ContentType.Split("/");
+ content.Type = splittedMimeType[0];
+ content.Subtype = splittedMimeType[1];
+
+ // Set the file name for the receiver, by default the same name is taken.
+ // This line is useful only for the explanation.
+ content.Name = fileCopy.Name;
+
+ return content;
+ }
+
+ private async Task OpenMicrophonePopup()
+ {
+ AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
+ CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
+ AudioGraph audioGraph = result.Graph;
+
+ CreateAudioDeviceInputNodeResult resultNode = await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media);
+ AudioDeviceInputNode deviceInputNode = resultNode.DeviceInputNode;
+
+ deviceInputNode.Dispose();
+ audioGraph.Dispose();
+ }
+
+ private async Task OpenCameraPopup()
+ {
+ MediaCapture mediaCapture = new Windows.Media.Capture.MediaCapture();
+ await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
+ {
+ StreamingCaptureMode = StreamingCaptureMode.Video
+ });
+ mediaCapture.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Service/NavigationService.cs b/uwp/cs/05_FileTransfer/Service/NavigationService.cs
new file mode 100644
index 0000000..aeeaa00
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Service/NavigationService.cs
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _05_FileTransfer.Views;
+
+namespace _05_FileTransfer.Service
+{
+ internal class NavigationService
+ {
+ private static readonly NavigationService instance = new NavigationService();
+
+ public static NavigationService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ public NavigationRoot CurrentNavigationRoot { get; set; }
+
+ public ChatsPage CurrentChatspage { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Service/VideoService.cs b/uwp/cs/05_FileTransfer/Service/VideoService.cs
new file mode 100644
index 0000000..dd0e227
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Service/VideoService.cs
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Windows.UI.Xaml.Controls;
+
+namespace _05_FileTransfer.Service
+{
+ internal class VideoService
+ {
+ private static readonly VideoService instance = new VideoService();
+
+ public static VideoService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public void StartVideoStream(SwapChainPanel main, SwapChainPanel preview)
+ {
+ CoreService.Core.NativePreviewWindowId = preview;
+ CoreService.Core.NativeVideoWindowId = main;
+ }
+
+ public void StopVideoStream()
+ {
+ CoreService.Core.NativePreviewWindowId = null;
+ CoreService.Core.NativeVideoWindowId = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/CallsPage.xaml b/uwp/cs/05_FileTransfer/Views/CallsPage.xaml
new file mode 100644
index 0000000..3524a26
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/CallsPage.xaml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/CallsPage.xaml.cs b/uwp/cs/05_FileTransfer/Views/CallsPage.xaml.cs
new file mode 100644
index 0000000..eeda09d
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/CallsPage.xaml.cs
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _05_FileTransfer.Service;
+using Linphone;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _05_FileTransfer.Views
+{
+ public sealed partial class CallsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private VideoService VideoService { get; } = VideoService.Instance;
+
+ private Call IncommingCall;
+
+ public CallsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ VideoService.StopVideoStream();
+ CoreService.RemoveOnCallStateChangedDelegate(OnCallStateChanged);
+ base.OnNavigatedFrom(e);
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ HelloText.Text += CoreService.Core.DefaultProxyConfig.FindAuthInfo().Username;
+ CoreService.AddOnCallStateChangedDelegate(OnCallStateChanged);
+
+ if (CoreService.Core.CurrentCall != null)
+ {
+ OnCallStateChanged(CoreService.Core, CoreService.Core.CurrentCall, CoreService.Core.CurrentCall.State, null);
+ }
+ }
+
+ private void CallClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Call(UriToCall.Text);
+ }
+
+ private void HangOutClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Core.TerminateAllCalls();
+ }
+
+ private void SoundClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.SpeakerMutedSwitch())
+ {
+ Sound.Content = "Switch on Sound";
+ }
+ else
+ {
+ Sound.Content = "Switch off Sound";
+ }
+ }
+
+ private async void CameraClick(object sender, RoutedEventArgs e)
+ {
+ await CoreService.CameraEnabledSwitchAsync();
+ Camera.Content = "Waiting for accept ...";
+ Camera.IsEnabled = false;
+ }
+
+ private void MicClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.MicEnabledSwitch())
+ {
+ Mic.Content = "Mute";
+ }
+ else
+ {
+ Mic.Content = "Unmute";
+ }
+ }
+
+ private void AnswerClick(object sender, RoutedEventArgs e)
+ {
+ IncommingCall.Accept();
+ IncommingCall = null;
+ }
+
+ private void DeclineClick(object sender, RoutedEventArgs e)
+ {
+ if (IncommingCall != null)
+ {
+ IncommingCall.Decline(Reason.Declined);
+ IncommingCall = null;
+ }
+ }
+
+ private void OnCallStateChanged(Core core, Call call, CallState state, string message)
+ {
+ CallText.Text = "Your call state is : " + state.ToString();
+ switch (state)
+ {
+ case CallState.IncomingReceived:
+
+ IncommingCall = call;
+ IncomingCallStackPanel.Visibility = Visibility.Visible;
+ IncommingCallText.Text = " " + call.RemoteAddress.AsString();
+ break;
+
+ case CallState.OutgoingInit:
+ case CallState.OutgoingProgress:
+ case CallState.OutgoingRinging:
+
+ HangOut.IsEnabled = true;
+ break;
+
+ case CallState.StreamsRunning:
+ case CallState.UpdatedByRemote:
+
+ CallInProgressGuiUpdates();
+ if (call.CurrentParams.VideoEnabled)
+ {
+ StartVideoAndUpdateGui();
+ }
+ else
+ {
+ StopVideoAndUpdateGui();
+ }
+ break;
+
+ case CallState.Error:
+ case CallState.End:
+ case CallState.Released:
+
+ IncommingCall = null;
+ EndingCallGuiUpdates();
+ VideoService.StopVideoStream();
+ break;
+ }
+ }
+
+ private void StopVideoAndUpdateGui()
+ {
+ Camera.Content = "Switch on Camera";
+ Camera.IsEnabled = true;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ VideoService.StopVideoStream();
+ }
+
+ private void StartVideoAndUpdateGui()
+ {
+ VideoGrid.Visibility = Visibility.Visible;
+ Camera.Content = "Switch off Camera";
+ VideoService.StartVideoStream(VideoSwapChainPanel, PreviewSwapChainPanel);
+ Camera.IsEnabled = true;
+ }
+
+ private void EndingCallGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = true;
+ HangOut.IsEnabled = false;
+ Sound.IsEnabled = false;
+ Camera.IsEnabled = false;
+ Mic.IsEnabled = false;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ Camera.Content = "Switch on Camera";
+ Mic.Content = "Mute";
+ Sound.Content = "Switch off Sound";
+ }
+
+ private void CallInProgressGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = false;
+ HangOut.IsEnabled = true;
+ Sound.IsEnabled = true;
+ Camera.IsEnabled = true;
+ Mic.IsEnabled = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/ChatPage.xaml b/uwp/cs/05_FileTransfer/Views/ChatPage.xaml
new file mode 100644
index 0000000..cb0b31d
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/ChatPage.xaml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/ChatPage.xaml.cs b/uwp/cs/05_FileTransfer/Views/ChatPage.xaml.cs
new file mode 100644
index 0000000..ef89ec4
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/ChatPage.xaml.cs
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _05_FileTransfer.Controls;
+using _05_FileTransfer.Service;
+using Linphone;
+using System;
+using Windows.Storage;
+using Windows.Storage.Pickers;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _05_FileTransfer.Views
+{
+ public sealed partial class ChatPage : Page
+ {
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private ChatRoom ChatRoom;
+
+ public ChatPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ ChatRoom = ((ChatRoom)e.Parameter);
+ ChatHeaderText.Text += ChatRoom.PeerAddress.Username;
+ ChatRoom.Listener.OnMessageReceived += OnMessageReceived;
+ foreach (ChatMessage chatMessage in ChatRoom.GetHistory(0))
+ {
+ AddMessage(chatMessage);
+ }
+ ChatRoom.MarkAsRead();
+
+ NavigationService.CurrentNavigationRoot.UpdateUnreadMessageCount();
+ NavigationService.CurrentChatspage.UpdateChatRooms();
+
+ PeerUsername.Text += ChatRoom.PeerAddress.Username;
+ YourUsername.Text += ChatRoom.LocalAddress.Username;
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ ChatRoom.Listener.OnMessageReceived -= OnMessageReceived;
+ }
+
+ private void OnMessageReceived(ChatRoom chatRoom, ChatMessage message)
+ {
+ if (ChatRoom != null)
+ {
+ AddMessage(message);
+ chatRoom.MarkAsRead();
+ }
+ }
+
+ private void AddMessage(ChatMessage chatMessage)
+ {
+ // Instead of simply display a TextBlock we now create a
+ // MessageDisplay object to show more informations about the message.
+ // See Controls/MessageDisplay.xaml(.cs)
+ MessageDisplay messageDisplay = new MessageDisplay(chatMessage);
+
+ MessagesList.Children.Add(messageDisplay);
+
+ ScrollToBottom();
+ }
+
+ private void ScrollToBottom()
+ {
+ MessagesScroll.UpdateLayout();
+ MessagesScroll.ChangeView(1, MessagesScroll.ExtentHeight, 1);
+ }
+
+ private void OutgoingMessageButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (ChatRoom != null && OutgoingMessageText.Text != null && OutgoingMessageText.Text.Length > 0)
+ {
+ ChatMessage chatMessage = ChatRoom.CreateMessage(OutgoingMessageText.Text);
+ chatMessage.Send();
+ AddMessage(chatMessage);
+ }
+ OutgoingMessageText.Text = "";
+ }
+
+ ///
+ /// Method called when the "Send file" button is clicked
+ ///
+ private async void SendFileButton_Click(object sender, RoutedEventArgs e)
+ {
+ // Basic Windows code to let the user select a file and gain
+ // read access to a StorageFile object.
+ FileOpenPicker picker = new FileOpenPicker();
+ picker.ViewMode = PickerViewMode.List;
+ picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
+ picker.FileTypeFilter.Add("*");
+ StorageFile file = await picker.PickSingleFileAsync();
+
+ if (file != null)
+ {
+ // We create a Linphone.Content object from the StorageFile object
+ // see CoreService.CreateContentFromFile(StorageFile file)
+ Content content = await CoreService.CreateContentFromFile(file);
+
+ // To create a text ChatMessage for a chat room we use ChatRoom.CreateMessage(string message);
+ // Here we want to create a file transfer message so we must use
+ // ChatRoom.CreateFileTransferMessage(Content initialContent) to create it.
+ ChatMessage fileMessage = ChatRoom.CreateFileTransferMessage(content);
+
+ // Then simply call ChatMessage.Send() to send the message to the remote.
+ fileMessage.Send();
+
+ AddMessage(fileMessage);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/ChatsPage.xaml b/uwp/cs/05_FileTransfer/Views/ChatsPage.xaml
new file mode 100644
index 0000000..e5f59ab
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/ChatsPage.xaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/ChatsPage.xaml.cs b/uwp/cs/05_FileTransfer/Views/ChatsPage.xaml.cs
new file mode 100644
index 0000000..cb5395f
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/ChatsPage.xaml.cs
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _05_FileTransfer.Service;
+using Linphone;
+using System;
+using System.Threading.Tasks;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _05_FileTransfer.Views
+{
+ public sealed partial class ChatsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+
+ public ChatsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ NavigationService.CurrentChatspage = this;
+ UpdateChatRooms();
+ CoreService.AddOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
+ CoreService.AddOnMessageSentDelegate(OnMessageReceiveOrSent);
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ NavigationService.CurrentChatspage = null;
+ CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
+ CoreService.RemoveOnMessageSentDelegate(OnMessageReceiveOrSent);
+ base.OnNavigatedFrom(e);
+ }
+
+ private void OnMessageReceiveOrSent(Core core, ChatRoom chatRoom, ChatMessage message) => UpdateChatRooms();
+
+ public void UpdateChatRooms()
+ {
+ ChatRoom selectedChatRoom = (ChatRoom)ChatRoomsLV.SelectedItem;
+ ChatRoomsLV.Items.Clear();
+
+ foreach (ChatRoom chatRoom in CoreService.Core.ChatRooms)
+ {
+ if (chatRoom.HistorySize > 0)
+ {
+ ChatRoomsLV.Items.Add(chatRoom);
+ if (selectedChatRoom == chatRoom)
+ {
+ ChatRoomsLV.SelectedItem = chatRoom;
+ }
+ }
+ }
+ }
+
+ private void ChatRoomsLV_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ ChatRoomsLV.SelectedItem = e.ClickedItem;
+ ChatRoomFrame.Navigate(typeof(ChatPage), e.ClickedItem);
+ }
+
+ private async void NewChatRoom_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ string peerSipAddress = await InputTextDialogAsync("Enter peer sip address");
+ if (!String.IsNullOrWhiteSpace(peerSipAddress))
+ {
+ ChatRoom newChatRoom = CoreService.CreateOrGetChatRoom(peerSipAddress);
+ if (newChatRoom != null)
+ {
+ ChatRoomFrame.Navigate(typeof(ChatPage), newChatRoom);
+ }
+ else
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "ChatRoom creation error",
+ Content = "An error occurred during ChatRoom creation, check sip address validity and try again.",
+ CloseButtonText = "OK"
+ };
+
+ await noSettingsDialog.ShowAsync();
+ }
+ }
+ }
+
+ private async Task InputTextDialogAsync(string title)
+ {
+ TextBox inputTextBox = new TextBox
+ {
+ AcceptsReturn = false,
+ Height = 32
+ };
+ ContentDialog dialog = new ContentDialog
+ {
+ Content = inputTextBox,
+ Title = title,
+ IsSecondaryButtonEnabled = true,
+ PrimaryButtonText = "OK",
+ SecondaryButtonText = "Cancel"
+ };
+ if (await dialog.ShowAsync() == ContentDialogResult.Primary)
+ return inputTextBox.Text;
+ else
+ return "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/LoginPage.xaml b/uwp/cs/05_FileTransfer/Views/LoginPage.xaml
new file mode 100644
index 0000000..89b9ce3
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/LoginPage.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/LoginPage.xaml.cs b/uwp/cs/05_FileTransfer/Views/LoginPage.xaml.cs
new file mode 100644
index 0000000..86cb07f
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/LoginPage.xaml.cs
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _05_FileTransfer.Service;
+using Linphone;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _05_FileTransfer.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class LoginPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public LoginPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ CoreService.AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ }
+
+ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+ {
+ CoreService.RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ base.OnNavigatingFrom(e);
+ }
+
+ ///
+ /// Called when you click on the "Login" button.
+ ///
+ private void LogInClick(object sender, RoutedEventArgs e)
+ {
+ if (LogIn.IsEnabled)
+ {
+ LogIn.IsEnabled = false;
+
+ CoreService.LogIn(Identity.Text, Password.Password);
+ }
+ }
+
+ ///
+ /// Called when a key is pressed and released on the login page.
+ /// If you pressed "Enter", simulate a login click.
+ ///
+ private void GridKeyUp(object sender, KeyRoutedEventArgs e)
+ {
+ if (VirtualKey.Enter.Equals(e.Key))
+ {
+ LogInClick(null, null);
+ }
+ }
+
+ ///
+ /// This method is called every time the RegistrationState is updated by background core's actions.
+ /// In this example we use this to update the GUI.
+ ///
+ private void OnAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, string message)
+ {
+ RegistrationText.Text = "Your registration state is : " + state.ToString();
+ switch (state)
+ {
+ case RegistrationState.Cleared:
+ case RegistrationState.None:
+ CoreService.ClearCoreAfterLogOut();
+ LogIn.IsEnabled = true;
+ break;
+
+ case RegistrationState.Ok:
+ LogIn.IsEnabled = false;
+ this.Frame.Navigate(typeof(NavigationRoot));
+ break;
+
+ case RegistrationState.Progress:
+ LogIn.IsEnabled = false;
+ break;
+
+ case RegistrationState.Failed:
+ LogIn.IsEnabled = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/NavigationRoot.xaml b/uwp/cs/05_FileTransfer/Views/NavigationRoot.xaml
new file mode 100644
index 0000000..01bd47e
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/NavigationRoot.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/05_FileTransfer/Views/NavigationRoot.xaml.cs b/uwp/cs/05_FileTransfer/Views/NavigationRoot.xaml.cs
new file mode 100644
index 0000000..5b6fa2e
--- /dev/null
+++ b/uwp/cs/05_FileTransfer/Views/NavigationRoot.xaml.cs
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _05_FileTransfer.Service;
+using Linphone;
+using System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _05_FileTransfer.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class NavigationRoot : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+ private bool hasLoadedPreviously;
+
+ public NavigationRoot()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ this.CoreService.AddOnOnMessageReceivedDelegate(OnMessageReveive);
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ this.CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReveive);
+ base.OnNavigatedFrom(e);
+ }
+
+ private void Page_Loaded(object sender, RoutedEventArgs e)
+ {
+ // Only do an inital navigate the first time the page loads
+ // when we switch out of compactoverloadmode this will fire but we don't want to navigate because
+ // there is already a page loaded
+ if (!hasLoadedPreviously)
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ UpdateUnreadMessageCount();
+ hasLoadedPreviously = true;
+ NavigationService.CurrentNavigationRoot = this;
+ }
+ }
+
+ private void AppNavFrame_Navigated(object sender, NavigationEventArgs e)
+ {
+ switch (e.SourcePageType)
+ {
+ case Type c when e.SourcePageType == typeof(CallsPage):
+ ((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
+ break;
+
+ case Type c when e.SourcePageType == typeof(ChatsPage):
+ ((NavigationViewItem)navview.MenuItems[1]).IsSelected = true;
+ break;
+ }
+ }
+
+ private async void Navview_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
+ {
+ if (args.IsSettingsInvoked)
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "No settings",
+ Content = "There is no settings in this little app",
+ CloseButtonText = "OK"
+ };
+
+ ContentDialogResult result = await noSettingsDialog.ShowAsync();
+ return;
+ }
+
+ string invokedItemValue = args.InvokedItem as string;
+ if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ }
+ else
+ {
+ AppNavFrame.Navigate(typeof(ChatsPage));
+ }
+ }
+
+ private void SignOut_Tapped(object sender, TappedRoutedEventArgs e)
+ {
+ DisplaySignOutDialog();
+ }
+
+ private async void DisplaySignOutDialog()
+ {
+ ContentDialog signOutDialog = new ContentDialog
+ {
+ Title = "Sign out ?",
+ Content = "All your current calls and actions will be canceled, are you sure to continue ?",
+ PrimaryButtonText = "Sign out",
+ CloseButtonText = "Cancel"
+ };
+
+ ContentDialogResult result = await signOutDialog.ShowAsync();
+
+ if (result == ContentDialogResult.Primary)
+ {
+ CoreService.Core.TerminateAllCalls();
+ CoreService.LogOut();
+
+ this.Frame.Navigate(typeof(LoginPage));
+ }
+ }
+
+ private void OnMessageReveive(Core core, ChatRoom chatRoom, ChatMessage message)
+ {
+ UpdateUnreadMessageCount();
+ }
+
+ public void UpdateUnreadMessageCount()
+ {
+ if (CoreService.Core.UnreadChatMessageCountFromActiveLocals > 0)
+ {
+ NewMessageCount.Text = "" + CoreService.Core.UnreadChatMessageCountFromActiveLocals;
+ NewMessageCountBorder.Visibility = Visibility.Visible;
+ }
+ else
+ {
+ NewMessageCountBorder.Visibility = Visibility.Collapsed;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/06_GroupChat.csproj b/uwp/cs/06_GroupChat/06_GroupChat.csproj
new file mode 100644
index 0000000..a33a7cc
--- /dev/null
+++ b/uwp/cs/06_GroupChat/06_GroupChat.csproj
@@ -0,0 +1,248 @@
+
+
+
+
+ Debug
+ x86
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}
+ AppContainerExe
+ Properties
+ _06_GroupChat
+ 06_GroupChat
+ en-US
+ UAP
+ 10.0.19041.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ true
+ false
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+ App.xaml
+
+
+ ContentDisplay.xaml
+
+
+ GroupChatDisplay.xaml
+
+
+ EventDisplay.xaml
+
+
+ MessageDisplay.xaml
+
+
+
+
+
+
+ CallsPage.xaml
+
+
+
+ ChatPage.xaml
+
+
+ ChatsPage.xaml
+
+
+ CreateGroupChatRoom.xaml
+
+
+ LoginPage.xaml
+
+
+
+ NavigationRoot.xaml
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+
+
+
+ 2.1.13
+
+
+ 5.1.0-alpha.56
+
+
+ 6.2.11
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/App.xaml b/uwp/cs/06_GroupChat/App.xaml
new file mode 100644
index 0000000..91fbcd2
--- /dev/null
+++ b/uwp/cs/06_GroupChat/App.xaml
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/App.xaml.cs b/uwp/cs/06_GroupChat/App.xaml.cs
new file mode 100644
index 0000000..a177ccd
--- /dev/null
+++ b/uwp/cs/06_GroupChat/App.xaml.cs
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _06_GroupChat.Service;
+using _06_GroupChat.Views;
+using Linphone;
+using System;
+using System.Diagnostics;
+using System.Text;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _06_GroupChat
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Start Linphone
+ LoggingService.Instance.LogLevel = LogLevel.Debug;
+ LoggingService.Instance.Listener.OnLogMessageWritten = OnLog;
+
+ CoreService.CoreStart(Windows.ApplicationModel.Core.CoreApplication.GetCurrentView().CoreWindow.Dispatcher);
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ if (e.PrelaunchActivated == false)
+ {
+ if (rootFrame.Content == null)
+ {
+ // When the navigation stack isn't restored navigate to the first page,
+ // configuring the new page by passing required information as a navigation
+ // parameter
+ rootFrame.Navigate(typeof(LoginPage), e.Arguments);
+ }
+ // Ensure the current window is active
+ Window.Current.Activate();
+ }
+ }
+
+ private void OnLog(LoggingService logService, string domain, LogLevel lev, string message)
+ {
+ StringBuilder builder = new StringBuilder();
+ _ = builder.Append("Linphone-[").Append(lev.ToString()).Append("](").Append(domain).Append(")").Append(message);
+ Debug.WriteLine(builder.ToString());
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Assets/LockScreenLogo.scale-200.png b/uwp/cs/06_GroupChat/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/uwp/cs/06_GroupChat/Assets/LockScreenLogo.scale-200.png differ
diff --git a/uwp/cs/06_GroupChat/Assets/SplashScreen.scale-200.png b/uwp/cs/06_GroupChat/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/uwp/cs/06_GroupChat/Assets/SplashScreen.scale-200.png differ
diff --git a/uwp/cs/06_GroupChat/Assets/Square150x150Logo.scale-200.png b/uwp/cs/06_GroupChat/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/uwp/cs/06_GroupChat/Assets/Square150x150Logo.scale-200.png differ
diff --git a/uwp/cs/06_GroupChat/Assets/Square44x44Logo.scale-200.png b/uwp/cs/06_GroupChat/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/uwp/cs/06_GroupChat/Assets/Square44x44Logo.scale-200.png differ
diff --git a/uwp/cs/06_GroupChat/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/uwp/cs/06_GroupChat/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/uwp/cs/06_GroupChat/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/uwp/cs/06_GroupChat/Assets/StoreLogo.png b/uwp/cs/06_GroupChat/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/uwp/cs/06_GroupChat/Assets/StoreLogo.png differ
diff --git a/uwp/cs/06_GroupChat/Assets/Wide310x150Logo.scale-200.png b/uwp/cs/06_GroupChat/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/uwp/cs/06_GroupChat/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/uwp/cs/06_GroupChat/Controls/ContentDisplay.xaml b/uwp/cs/06_GroupChat/Controls/ContentDisplay.xaml
new file mode 100644
index 0000000..7ffbca3
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Controls/ContentDisplay.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Controls/ContentDisplay.xaml.cs b/uwp/cs/06_GroupChat/Controls/ContentDisplay.xaml.cs
new file mode 100644
index 0000000..7692646
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Controls/ContentDisplay.xaml.cs
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.IO;
+using Windows.Storage;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace _06_GroupChat.Controls
+{
+ public sealed partial class ContentDisplay : UserControl
+ {
+ private readonly ChatMessage ChatMessage;
+ private readonly Content DisplayedContent;
+
+ public ContentDisplay(Content content, ChatMessage chatMessage)
+ {
+ this.InitializeComponent();
+ DisplayedContent = content;
+ ChatMessage = chatMessage;
+ UpdateLayoutFromContent();
+ }
+
+ private void UpdateLayoutFromContent()
+ {
+ // We kept the code from the old MessageDisplay class. Working
+ // with a single object instead of a List of content make it
+ // cleaner.
+ if (DisplayedContent.IsFile || DisplayedContent.IsFileTransfer)
+ {
+ TextStack.Visibility = Visibility.Collapsed;
+ FileStack.Visibility = Visibility.Visible;
+
+ FileName.Text = DisplayedContent.Name;
+ FileSize.Text = DisplayedContent.FileSize + " bits";
+
+ if (DisplayedContent.IsFile || DisplayedContent.IsFileTransfer && ChatMessage.IsOutgoing)
+ {
+ OpenFile.Visibility = Visibility.Visible;
+ Download.Visibility = Visibility.Collapsed;
+ }
+ else
+ {
+ Download.Visibility = Visibility.Visible;
+ OpenFile.Visibility = Visibility.Collapsed;
+ }
+ }
+ else if (DisplayedContent.IsText)
+ {
+ TextStack.Visibility = Visibility.Visible;
+ FileStack.Visibility = Visibility.Collapsed;
+ TextMessage.Text = DisplayedContent.Utf8Text;
+ }
+ }
+
+ // The download and open file click method are the same as before
+ private void Download_Click(object sender, RoutedEventArgs e)
+ {
+ Download.Visibility = Visibility.Collapsed;
+ FileSize.Text = "Download in progress ...";
+
+ string downloadPathFolder = ApplicationData.Current.LocalFolder.Path + @"\Downloads\";
+ Directory.CreateDirectory(downloadPathFolder);
+ DisplayedContent.FilePath = downloadPathFolder + DisplayedContent.Name;
+
+ ChatMessage.DownloadContent(DisplayedContent);
+ }
+
+ private async void OpenFile_Click(object sender, RoutedEventArgs e)
+ {
+ string filePath = DisplayedContent.FilePath;
+ string folderPath = filePath.Substring(0, filePath.LastIndexOf("\\"));
+
+ await Launcher.LaunchFolderAsync(await StorageFolder.GetFolderFromPathAsync(folderPath));
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Controls/EventDisplay.xaml b/uwp/cs/06_GroupChat/Controls/EventDisplay.xaml
new file mode 100644
index 0000000..bd0cddf
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Controls/EventDisplay.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Controls/EventDisplay.xaml.cs b/uwp/cs/06_GroupChat/Controls/EventDisplay.xaml.cs
new file mode 100644
index 0000000..b87702e
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Controls/EventDisplay.xaml.cs
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using Windows.UI.Xaml.Controls;
+
+namespace _06_GroupChat.Controls
+{
+ public sealed partial class EventDisplay : UserControl
+ {
+ public EventDisplay(EventLog eventLog)
+ {
+ // An EventDisplay is always linked to an EventLog object and is displayed at the center
+ // of the message list.
+ this.InitializeComponent();
+
+ // After we simply create the text we want to display based on the type on event
+ // and from information we get from the EventLog object.
+ switch (eventLog.Type)
+ {
+ case EventLogType.ConferenceCreated:
+ // For example here we use the Subject attribute to display the name of the conference
+ // when it is created.
+ EventText.Text = $"The conference {eventLog.Subject} is created";
+ break;
+
+ case EventLogType.ConferenceTerminated:
+ EventText.Text = $"The conference {eventLog.Subject} is terminated";
+ break;
+
+ case EventLogType.ConferenceCallStart:
+ EventText.Text = "Call start";
+ break;
+
+ case EventLogType.ConferenceCallEnd:
+ EventText.Text = "Call end";
+ break;
+
+ case EventLogType.ConferenceParticipantAdded:
+ // Or you can access a ParticipantAddress attribute when the type of
+ // event is linked to a participant.
+ EventText.Text = $"{eventLog.ParticipantAddress.Username} is added";
+ break;
+
+ case EventLogType.ConferenceParticipantRemoved:
+ EventText.Text = $"{eventLog.ParticipantAddress.Username} is removed";
+ break;
+
+ case EventLogType.ConferenceParticipantSetAdmin:
+ EventText.Text = $"{eventLog.ParticipantAddress.Username} is now admin";
+ break;
+
+ case EventLogType.ConferenceParticipantUnsetAdmin:
+ EventText.Text = $"{eventLog.ParticipantAddress.Username} admin status removed";
+ break;
+
+ case EventLogType.ConferenceSubjectChanged:
+ EventText.Text = $"The conference subject is now {eventLog.Subject}";
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Controls/GroupChatDisplay.xaml b/uwp/cs/06_GroupChat/Controls/GroupChatDisplay.xaml
new file mode 100644
index 0000000..7d5fe31
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Controls/GroupChatDisplay.xaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+ Participants :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Controls/GroupChatDisplay.xaml.cs b/uwp/cs/06_GroupChat/Controls/GroupChatDisplay.xaml.cs
new file mode 100644
index 0000000..ded89d9
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Controls/GroupChatDisplay.xaml.cs
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _06_GroupChat.Service;
+using _06_GroupChat.Shared;
+using Linphone;
+using System;
+using System.Linq;
+using Windows.UI.Xaml.Controls;
+
+namespace _06_GroupChat.Controls
+{
+ public sealed partial class GroupChatDisplay : UserControl
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private readonly ChatRoom ChatRoom;
+
+ public GroupChatDisplay(ChatRoom chatRoom)
+ {
+ this.InitializeComponent();
+ ChatRoom = chatRoom;
+ }
+
+ private void GroupChatDisplay_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ UpdateList();
+ UpdateGuiFromAdminState();
+
+ // We register to those callbacks so every time the participant list is
+ // modified we can update our GUI, see OnParticipantListUpdate and UpdateList.
+ ChatRoom.Listener.OnParticipantAdded += OnParticipantListUpdate;
+ ChatRoom.Listener.OnParticipantRemoved += OnParticipantListUpdate;
+
+ // Every time the admin status of one participant is changed this callback is called.
+ // We use this to update GUI because we display the admin status of each participant,
+ // see OnParticipantAdminStatusChanged and UpdateList. We also made a check to our admin
+ // status on this ChatRoom to see if we have access to the different controls (rename chat
+ // room, add/remove participant...)
+ ChatRoom.Listener.OnParticipantAdminStatusChanged += OnParticipantAdminStatusChanged;
+ }
+
+ private void GroupChatDisplay_Unloaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ // Don't forget to unregister to avoid memory leak
+ ChatRoom.Listener.OnParticipantAdded -= OnParticipantListUpdate;
+ ChatRoom.Listener.OnParticipantRemoved -= OnParticipantListUpdate;
+ ChatRoom.Listener.OnParticipantAdminStatusChanged -= OnParticipantAdminStatusChanged;
+ }
+
+ private void OnParticipantAdminStatusChanged(ChatRoom chatRoom, EventLog eventLog)
+ {
+ UpdateList();
+ UpdateGuiFromAdminState();
+ }
+
+ private void UpdateGuiFromAdminState()
+ {
+ // We check out our admin status with ChatRoom.Me.IsAdmin and if we
+ // aren't admin the controls are disabled.
+ AddParticipant.IsEnabled = ChatRoom.Me.IsAdmin;
+ foreach (var control in GroupChatDisplayGrid.Children.OfType())
+ {
+ control.IsEnabled = ChatRoom.Me.IsAdmin;
+ }
+ }
+
+ private void OnParticipantListUpdate(ChatRoom chatRoom, EventLog eventLog) => UpdateList();
+
+ private void UpdateList()
+ {
+ ParticipantsLV.Items.Clear();
+
+ // You can find the participant list in the ChatRoom.Participants attribute.
+ // You can note that the participant list doesn't contain yourself.
+ foreach (Participant participant in ChatRoom.Participants)
+ {
+ if (participant.Address != null && !String.IsNullOrWhiteSpace(participant.Address.Username))
+ {
+ ParticipantsLV.Items.Add(participant);
+ }
+ }
+ }
+
+ ///
+ /// This method is called when the remove button near a participant username is clicked.
+ ///
+ private void Remove_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ Participant participantToRemove = (Participant)((Button)sender).Tag;
+
+ // To remove a participant simply use the RemoveParticipant(Participant participant) method
+ // on a ChatRoom object. If you are admin and the participant is present in the ChatRoom he
+ // will be removed.
+ // The method RemoveParticipants(IEnumerable participants) also exist if you want to
+ // remove multiple participant at once.
+ ChatRoom.RemoveParticipant(participantToRemove);
+ }
+
+ ///
+ /// This method is called when the button to add a participant is clicked.
+ ///
+ private async void AddParticipant_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ string peerSipAddress = await Utils.InputTextDialogAsync("Enter peer sip address");
+ Address address = CoreService.Core.InterpretUrl(peerSipAddress);
+ if (address != null)
+ {
+ // To add a participant simply call the method AddParticipant(Address addr).
+ // If you are admin and the participant have a device that can handle
+ // group chat connected to the conference server he will be added.
+ // You can use AddParticipants(IEnumerable addresses) to add multiple
+ // participants at once.
+ // Here we use Core.InterpretUrl to transform a string sip address to a valid
+ // Linphone.Address object as we done multiple times before.
+ ChatRoom.AddParticipant(address);
+ }
+ else
+ {
+ ContentDialog badAddressDialog = new ContentDialog
+ {
+ Title = "Adding participant failed",
+ Content = "An error occurred during address interpretation, check sip address validity and try again.",
+ CloseButtonText = "OK"
+ };
+
+ await badAddressDialog.ShowAsync();
+ }
+ }
+
+ ///
+ /// This method is called when you switch the admin status of a participant
+ ///
+ private void AdminSwitch_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ Participant participantToUpgrade = (Participant)((Button)sender).Tag;
+
+ // Use the SetParticipantAdminStatus(Participant participant, bool isAdmin) to change
+ // the admin of a participant, you must be admin yourself if you want this action to work.
+ ChatRoom.SetParticipantAdminStatus(participantToUpgrade, !participantToUpgrade.IsAdmin);
+ }
+
+ ///
+ /// This method is called when the "Rename group chat" button is clicked
+ ///
+ private async void RenameGroupChat_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ string newName = await Utils.InputTextDialogAsync("Enter new name for group");
+
+ // To change the subject of a ChatRoom simply update the Subject attribute of a ChatRoom.
+ ChatRoom.Subject = newName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Controls/MessageDisplay.xaml b/uwp/cs/06_GroupChat/Controls/MessageDisplay.xaml
new file mode 100644
index 0000000..640de50
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Controls/MessageDisplay.xaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Controls/MessageDisplay.xaml.cs b/uwp/cs/06_GroupChat/Controls/MessageDisplay.xaml.cs
new file mode 100644
index 0000000..1821bd0
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Controls/MessageDisplay.xaml.cs
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace _06_GroupChat.Controls
+{
+ public sealed partial class MessageDisplay : UserControl
+ {
+ private readonly ChatMessage ChatMessage;
+
+ public MessageDisplay(ChatMessage message)
+ {
+ this.InitializeComponent();
+ ChatMessage = message;
+ UpdateLayoutFromMessage();
+ UpdateLayoutFromContents();
+ }
+
+ private void MessageDisplay_Loaded(object sender, RoutedEventArgs e)
+ {
+ ChatMessage.Listener.OnMsgStateChanged += OnMessageStateChanged;
+ }
+
+ private void MessageDisplay_Unloaded(object sender, RoutedEventArgs e)
+ {
+ ChatMessage.Listener = null;
+ }
+
+ private void OnMessageStateChanged(ChatMessage message, ChatMessageState state)
+ {
+ MessageState.Text = "The message state is : " + state;
+
+ switch (state)
+ {
+ case ChatMessageState.FileTransferError:
+ case ChatMessageState.FileTransferDone:
+ UpdateLayoutFromContents();
+ return;
+ }
+ }
+
+ private void UpdateLayoutFromMessage()
+ {
+ // Here we keep only the informations kept at the ChatMessage level
+ MessageState.Text = "The message state is : " + ChatMessage.State;
+ ReceiveDate.Text = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(ChatMessage.Time).ToLocalTime().ToString("HH:mm");
+ SenderName.Text += ChatMessage.FromAddress.Username;
+
+ if (ChatMessage.IsOutgoing)
+ {
+ this.HorizontalAlignment = HorizontalAlignment.Right;
+ }
+ else
+ {
+ this.HorizontalAlignment = HorizontalAlignment.Left;
+ }
+ }
+
+ private void UpdateLayoutFromContents()
+ {
+ ContentsStack.Children.Clear();
+
+ // We iterate over the Contents list to display all the contents
+ // in a multipart message.
+ // This code is common for Basic and Flexisip ChatRoom so even if
+ // another SIP client don't respect the basic chat room rules and
+ // and send multipart we can display it.
+ foreach (Content content in ChatMessage.Contents)
+ {
+ AddContent(content);
+ }
+ }
+
+ private void AddContent(Content content)
+ {
+ // A Content object can himself be multipart
+ if (content.IsMultipart)
+ {
+ // So we make this method recursive
+ foreach (Content innerContent in content.Parts)
+ {
+ AddContent(innerContent);
+ }
+ return;
+ }
+
+ // And we create a content display for each content. You can watch the code
+ // in content ContentDisplay.xaml(.cs).
+ ContentDisplay contentDisplay = new ContentDisplay(content, ChatMessage);
+ ContentsStack.Children.Add(contentDisplay);
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Package.appxmanifest b/uwp/cs/06_GroupChat/Package.appxmanifest
new file mode 100644
index 0000000..3d0b13e
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Package.appxmanifest
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+ 06_GroupChat
+ Anthony
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Properties/AssemblyInfo.cs b/uwp/cs/06_GroupChat/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..65ca1a7
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("06_GroupChat")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("06_GroupChat")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Properties/Default.rd.xml b/uwp/cs/06_GroupChat/Properties/Default.rd.xml
new file mode 100644
index 0000000..76edee8
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Properties/Default.rd.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Readme.md b/uwp/cs/06_GroupChat/Readme.md
new file mode 100644
index 0000000..4903401
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Readme.md
@@ -0,0 +1,65 @@
+Linphone X UWP tutorial 06_group_chat
+========================================
+
+In this step we are going to approach to new concepts: group chat and multipart message. To enable
+those new features we are going to use Flexisip as a backend (see group ChatRoom creation). Flexisip
+is a complete and modular SIP server suite. If you need more informations about Flexisip you can read
+this [Flexisip presentation](http://linphone.org/technical-corner/flexisip) or
+[Contact us](http://linphone.org/contact).
+
+We created a new page so you can prepare your participant list before creating a group chat, see
+CreateGroupChatRoom.xaml(.cs) and CoreService.cs to learn how to create group chat.
+
+We also updated the ChatPage.xaml(.cs) and the MessageDisplay.xaml(cs) so you can send and display multipart
+messages correctly. Multipart message are allowed by default in flexisip, you can try it right now in group chat room.
+In group ChatRoom some events that can occur (subject change, admin status modification...) are also displayed, see EventDisplay.xaml(.cs).
+And last we change the way we display the ChatRoom in the list (ChatsPage.xml) see ChatRoomToStringConverter.cs.
+
+
+Don't forget to install those NuGet packages :
+ - LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
+ - Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
+ - ANGLE.WindowsStore (for video rendering, version 2.1.13 recommended)
+
+New/updated files :
+
+```
+06_group_chat
+
+└───Controls :
+│ │ ContentDisplay.xaml(.cs) : Inner control now used in MessageDisplay. Contains the code to display
+│ │ one Linphone.Content object.
+│ │
+│ │ EventDisplay.xaml(.cs) : Inner control used in ChatPage to display all events that
+│ │ are not messages.
+│ │
+│ │ GroupChatDisplay.xaml(.cs) : A control used in the ChatPage to display the participant list
+│ │ and some group chat controls.
+│ │
+│ │ MessageDisplay.xaml(.cs) : A user control to display chat bubbles with more
+│ │ information. Improved from the previous step to display all the
+│ │ contents of multipart messages (using ContentDisplay).
+│ │
+│
+└───Service :
+│ │ CoreService.cs : A singleton service which contains the Linphone.Core.
+│ │ Watch the LogIn method to see how to setup a conference factory.
+│
+└───Shared :
+│ │ ChatRoomToStringConverter.cs : a class that implement IValueConverter to display the
+│ │ the chat room name according to its type.
+│ │
+│ │ Utils.cs : Utility class to regroup static methods used in different other classes.
+│
+└───Views :
+│ │
+│ │ ChatPage.xaml(.cs) : This is the frame displayed when you select a chat room.
+│ │ You can now send and receive multipart when you are in a group chat room.
+│ │ And when you open a group chat room you have access to a new menu with
+│ │ the participants list and new controls (see GroupChatDisplay).
+│ │
+│ │ ChatsPage.xaml(.cs) : ChatRoom list, updated to display different information according
+│ │ to the chat room type (also see ChatRoomToStringConverter.cs)
+│ │
+│ │
+```
diff --git a/uwp/cs/06_GroupChat/Service/CoreService.cs b/uwp/cs/06_GroupChat/Service/CoreService.cs
new file mode 100644
index 0000000..105db6a
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Service/CoreService.cs
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Media.Audio;
+using Windows.Media.Capture;
+using Windows.Storage;
+using Windows.UI.Core;
+using static Linphone.CoreListener;
+
+namespace _06_GroupChat.Service
+{
+ internal class CoreService
+ {
+ private Timer Timer;
+
+ private static readonly CoreService instance = new CoreService();
+
+ public static CoreService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private Core core;
+
+ public Core Core
+ {
+ get
+ {
+ if (core == null)
+ {
+ Factory factory = Factory.Instance;
+
+ string assetsPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share");
+ factory.TopResourcesDir = assetsPath;
+ factory.DataResourcesDir = assetsPath;
+ factory.SoundResourcesDir = Path.Combine(assetsPath, "sounds", "linphone");
+ factory.RingResourcesDir = Path.Combine(factory.SoundResourcesDir, "rings");
+ factory.ImageResourcesDir = Path.Combine(assetsPath, "images");
+ factory.MspluginsDir = ".";
+
+ // In a Flexisip ChatRoom you are identified by your authentication info and your device (you can have multiple device
+ // connected to your account, some can accept group chat and some cannot). To identify your different devices Linphone
+ // use UUID generated when your start your app for the first time on your device. This UUID is stored in a configuration
+ // file, this is why we specify a file for this configuration file now, if you don't every time you will start your app
+ // it will be identified as a new device.
+ // A second effect of this you will soon notify is that your authentication informations are also stored in this file
+ // and are loaded at core startup. So if you don't use the sign out button and simply close the app the next time you won't
+ // have to login, it will be automatic.
+ core = factory.CreateCore(Path.Combine(ApplicationData.Current.LocalFolder.Path, "configuration"), "", IntPtr.Zero);
+
+ core.AudioPort = 7666;
+ core.VideoPort = 9666;
+
+ core.RootCa = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share", "Linphone", "rootca.pem");
+ core.UserCertificatesPath = ApplicationData.Current.LocalFolder.Path;
+
+ VideoActivationPolicy videoActivationPolicy = factory.CreateVideoActivationPolicy();
+ videoActivationPolicy.AutomaticallyAccept = true;
+ videoActivationPolicy.AutomaticallyInitiate = false;
+ core.VideoActivationPolicy = videoActivationPolicy;
+
+ if (core.VideoSupported())
+ {
+ core.VideoCaptureEnabled = true;
+ }
+ core.UsePreviewWindow(true);
+
+ core.FileTransferServer = "https://www.linphone.org:444/lft.php";
+ }
+ return core;
+ }
+ }
+
+ public void CoreStart(CoreDispatcher dispatcher)
+ {
+ Core.Start();
+
+ Timer = new Timer(OnTimedEvent, dispatcher, 20, 20);
+ }
+
+ private async void OnTimedEvent(object state)
+ {
+ await ((CoreDispatcher)state).RunIdleAsync((args) =>
+ {
+ Core.Iterate();
+ });
+ }
+
+ public void AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged += myDelegate;
+ }
+
+ public void RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged -= myDelegate;
+ }
+
+ public void AddOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged += myDelegate;
+ }
+
+ public void RemoveOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged -= myDelegate;
+ }
+
+ public void AddOnOnMessageReceivedDelegate(OnMessageReceivedDelegate myDelegate)
+ {
+ Core.Listener.OnMessageReceived += myDelegate;
+ }
+
+ public void RemoveOnOnMessageReceivedDelegate(OnMessageReceivedDelegate myDelegate)
+ {
+ Core.Listener.OnMessageReceived -= myDelegate;
+ }
+
+ public void AddOnMessageSentDelegate(OnMessageSentDelegate myDelegate)
+ {
+ Core.Listener.OnMessageSent += myDelegate;
+ }
+
+ public void RemoveOnMessageSentDelegate(OnMessageSentDelegate myDelegate)
+ {
+ Core.Listener.OnMessageSent -= myDelegate;
+ }
+
+ public void AddOnChatRoomSubjectChangedDelegate(OnChatRoomSubjectChangedDelegate myDelegate)
+ {
+ Core.Listener.OnChatRoomSubjectChanged += myDelegate;
+ }
+
+ public void RemoveOnChatRoomSubjectChangedDelegate(OnChatRoomSubjectChangedDelegate myDelegate)
+ {
+ Core.Listener.OnChatRoomSubjectChanged -= myDelegate;
+ }
+
+ public void LogIn(string identity, string password)
+ {
+ Address address = Factory.Instance.CreateAddress(identity);
+ AuthInfo authInfo = Factory.Instance.CreateAuthInfo(address.Username, "", password, "", "", address.Domain);
+ Core.AddAuthInfo(authInfo);
+
+ AccountParams accountParams = Core.CreateAccountParams();
+ accountParams.IdentityAddress = address;
+ string serverAddr = "sip:" + address.Domain + ";transport=tls";
+ accountParams.ServerAddr = serverAddr;
+
+ accountParams.RegisterEnabled = true;
+
+ // If you want to create some group chats (conferences) you need to
+ // specify a conference factory URI. Here is the Linphone.org conference
+ // factory URI.
+ accountParams.ConferenceFactoryUri = "sip:conference-factory@sip.linphone.org";
+
+ Account account = Core.CreateAccount(accountParams);
+ Core.AddAccount(account);
+ Core.DefaultAccount = account;
+ }
+
+ public void LogOut()
+ {
+ Account account = Core.DefaultAccount;
+ if (account != null)
+ {
+ AccountParams accountParams = account.Params.Clone();
+ accountParams.RegisterEnabled = false;
+ account.Params = accountParams;
+ }
+ }
+
+ public void ClearCoreAfterLogOut()
+ {
+ Core.ClearAllAuthInfo();
+ Core.ClearAccounts();
+ }
+
+ public async void Call(string uriToCall)
+ {
+ await OpenMicrophonePopup();
+
+ Address address = Core.InterpretUrl(uriToCall);
+ Core.InviteAddress(address);
+ }
+
+ public bool MicEnabledSwitch()
+ {
+ return Core.MicEnabled = !Core.MicEnabled;
+ }
+
+ public bool SpeakerMutedSwitch()
+ {
+ return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
+ }
+
+ public async Task CameraEnabledSwitchAsync()
+ {
+ await OpenCameraPopup();
+
+ Call call = Core.CurrentCall;
+ CallParams param = core.CreateCallParams(call);
+ bool newValue = !param.VideoEnabled;
+ param.VideoEnabled = newValue;
+ call.Update(param);
+
+ return newValue;
+ }
+
+ public ChatRoom CreateOrGetChatRoom(string sipAddress)
+ {
+ Address remoteAddress = Core.InterpretUrl(sipAddress);
+ Address localAdress = Core.DefaultProxyConfig.IdentityAddress;
+
+ ChatRoomParams chatRoomParams = Core.CreateDefaultChatRoomParams();
+ chatRoomParams.Backend = ChatRoomBackend.Basic;
+ chatRoomParams.EncryptionBackend = ChatRoomEncryptionBackend.None;
+ chatRoomParams.EncryptionEnabled = false;
+ chatRoomParams.GroupEnabled = false;
+ chatRoomParams.RttEnabled = false;
+
+ return Core.CreateChatRoom(chatRoomParams, localAdress, new[] { remoteAddress });
+ }
+
+ public ChatRoom CreateGroupChatRoom(IEnumerable participants, string subject)
+ {
+ Address localAdress = Core.DefaultProxyConfig.IdentityAddress;
+
+ ChatRoomParams chatRoomParams = Core.CreateDefaultChatRoomParams();
+
+ // In comparison to basic chat rooms (see CreateOrGetChatRoom) you have two parameters
+ // to change. You must use a Flexisip backend and enable group.
+ chatRoomParams.Backend = ChatRoomBackend.FlexisipChat;
+ chatRoomParams.GroupEnabled = true;
+
+ // Set the subject of your chat room with what the user entered.
+ chatRoomParams.Subject = subject;
+
+ chatRoomParams.EncryptionBackend = ChatRoomEncryptionBackend.None;
+ chatRoomParams.EncryptionEnabled = false;
+ chatRoomParams.RttEnabled = false;
+
+ // Now you can create your group chat room. the participants list must be not empty.
+ // With the different information you send the conference factory will try to create your ChatRoom.
+ // See ChatPage.OnNavigatedTo to see how to know when your ChatRoom is ready.
+ return Core.CreateChatRoom(chatRoomParams, localAdress, participants);
+ }
+
+ public async Task CreateContentFromFile(StorageFile file)
+ {
+ StorageFile fileCopy = await file.CopyAsync(ApplicationData.Current.LocalFolder, file.Name, NameCollisionOption.ReplaceExisting);
+
+ Content content = Core.CreateContent();
+ content.FilePath = fileCopy.Path;
+
+ string[] splittedMimeType = fileCopy.ContentType.Split("/");
+ content.Type = splittedMimeType[0];
+ content.Subtype = splittedMimeType[1];
+
+ // Set the file name for the receiver
+ content.Name = fileCopy.Name;
+
+ return content;
+ }
+
+ private async Task OpenMicrophonePopup()
+ {
+ AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
+ CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
+ AudioGraph audioGraph = result.Graph;
+
+ CreateAudioDeviceInputNodeResult resultNode = await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media);
+ AudioDeviceInputNode deviceInputNode = resultNode.DeviceInputNode;
+
+ deviceInputNode.Dispose();
+ audioGraph.Dispose();
+ }
+
+ private async Task OpenCameraPopup()
+ {
+ MediaCapture mediaCapture = new Windows.Media.Capture.MediaCapture();
+ await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
+ {
+ StreamingCaptureMode = StreamingCaptureMode.Video
+ });
+ mediaCapture.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Service/NavigationService.cs b/uwp/cs/06_GroupChat/Service/NavigationService.cs
new file mode 100644
index 0000000..07c3ad0
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Service/NavigationService.cs
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _06_GroupChat.Views;
+
+namespace _06_GroupChat.Service
+{
+ internal class NavigationService
+ {
+ private static readonly NavigationService instance = new NavigationService();
+
+ public static NavigationService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ public NavigationRoot CurrentNavigationRoot { get; set; }
+
+ public ChatsPage CurrentChatspage { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Service/VideoService.cs b/uwp/cs/06_GroupChat/Service/VideoService.cs
new file mode 100644
index 0000000..f70b09f
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Service/VideoService.cs
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Windows.UI.Xaml.Controls;
+
+namespace _06_GroupChat.Service
+{
+ internal class VideoService
+ {
+ private static readonly VideoService instance = new VideoService();
+
+ public static VideoService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public void StartVideoStream(SwapChainPanel main, SwapChainPanel preview)
+ {
+ CoreService.Core.NativePreviewWindowId = preview;
+ CoreService.Core.NativeVideoWindowId = main;
+ }
+
+ public void StopVideoStream()
+ {
+ CoreService.Core.NativePreviewWindowId = null;
+ CoreService.Core.NativeVideoWindowId = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Shared/ChatRoomToStringConverter.cs b/uwp/cs/06_GroupChat/Shared/ChatRoomToStringConverter.cs
new file mode 100644
index 0000000..43f3024
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Shared/ChatRoomToStringConverter.cs
@@ -0,0 +1,44 @@
+using Linphone;
+using System;
+using System.Linq;
+using Windows.UI.Xaml.Data;
+
+namespace _06_GroupChat.Shared
+{
+ public class ChatRoomToStringConverter : IValueConverter
+ {
+ object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
+ {
+ // We use this converter to choose how to display the ChatRoom in the list.
+ ChatRoom chatRoom = (ChatRoom)value;
+ string nameInList = null;
+ if (chatRoom.HasCapability((int)ChatRoomCapabilities.Basic))
+ {
+ // For a basic ChatRoom we chose to display the peer Username
+ nameInList = chatRoom.PeerAddress.Username;
+ }
+ else if (chatRoom.HasCapability((int)ChatRoomCapabilities.OneToOne))
+ {
+ // If the ChatRoom is a OneToOne conference (we will speak more about those in further steps)
+ nameInList = chatRoom.Participants.FirstOrDefault() == null ? "" : chatRoom.Participants.First().Address.Username;
+ }
+ else if (chatRoom.HasCapability((int)ChatRoomCapabilities.Conference))
+ {
+ // The subject for a conference
+ nameInList = chatRoom.Subject;
+ }
+
+ if (String.IsNullOrEmpty(nameInList))
+ {
+ nameInList = "Incoherent ChatRoom values";
+ }
+
+ return nameInList;
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Shared/Utils.cs b/uwp/cs/06_GroupChat/Shared/Utils.cs
new file mode 100644
index 0000000..547e3bf
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Shared/Utils.cs
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.Threading.Tasks;
+using Windows.UI.Xaml.Controls;
+
+namespace _06_GroupChat.Shared
+{
+ public class Utils
+ {
+ public static async Task InputTextDialogAsync(string title)
+ {
+ TextBox inputTextBox = new TextBox
+ {
+ AcceptsReturn = false,
+ Height = 32
+ };
+ ContentDialog dialog = new ContentDialog
+ {
+ Content = inputTextBox,
+ Title = title,
+ IsSecondaryButtonEnabled = true,
+ PrimaryButtonText = "OK",
+ SecondaryButtonText = "Cancel"
+ };
+ if (await dialog.ShowAsync() == ContentDialogResult.Primary)
+ return inputTextBox.Text;
+ else
+ return "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/CallsPage.xaml b/uwp/cs/06_GroupChat/Views/CallsPage.xaml
new file mode 100644
index 0000000..9bd51db
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/CallsPage.xaml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/CallsPage.xaml.cs b/uwp/cs/06_GroupChat/Views/CallsPage.xaml.cs
new file mode 100644
index 0000000..6da2fad
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/CallsPage.xaml.cs
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _06_GroupChat.Service;
+using Linphone;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _06_GroupChat.Views
+{
+ public sealed partial class CallsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private VideoService VideoService { get; } = VideoService.Instance;
+
+ private Call IncommingCall;
+
+ public CallsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ VideoService.StopVideoStream();
+ CoreService.RemoveOnCallStateChangedDelegate(OnCallStateChanged);
+ base.OnNavigatedFrom(e);
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ HelloText.Text += CoreService.Core.DefaultProxyConfig.FindAuthInfo().Username;
+ CoreService.AddOnCallStateChangedDelegate(OnCallStateChanged);
+
+ if (CoreService.Core.CurrentCall != null)
+ {
+ OnCallStateChanged(CoreService.Core, CoreService.Core.CurrentCall, CoreService.Core.CurrentCall.State, null);
+ }
+ }
+
+ private void CallClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Call(UriToCall.Text);
+ }
+
+ private void HangOutClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Core.TerminateAllCalls();
+ }
+
+ private void SoundClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.SpeakerMutedSwitch())
+ {
+ Sound.Content = "Switch on Sound";
+ }
+ else
+ {
+ Sound.Content = "Switch off Sound";
+ }
+ }
+
+ private async void CameraClick(object sender, RoutedEventArgs e)
+ {
+ await CoreService.CameraEnabledSwitchAsync();
+ Camera.Content = "Waiting for accept ...";
+ Camera.IsEnabled = false;
+ }
+
+ private void MicClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.MicEnabledSwitch())
+ {
+ Mic.Content = "Mute";
+ }
+ else
+ {
+ Mic.Content = "Unmute";
+ }
+ }
+
+ private void AnswerClick(object sender, RoutedEventArgs e)
+ {
+ IncommingCall.Accept();
+ IncommingCall = null;
+ }
+
+ private void DeclineClick(object sender, RoutedEventArgs e)
+ {
+ if (IncommingCall != null)
+ {
+ IncommingCall.Decline(Reason.Declined);
+ IncommingCall = null;
+ }
+ }
+
+ private void OnCallStateChanged(Core core, Call call, CallState state, string message)
+ {
+ CallText.Text = "Your call state is : " + state.ToString();
+ switch (state)
+ {
+ case CallState.IncomingReceived:
+
+ IncommingCall = call;
+ IncomingCallStackPanel.Visibility = Visibility.Visible;
+ IncommingCallText.Text = " " + call.RemoteAddress.AsString();
+ break;
+
+ case CallState.OutgoingInit:
+ case CallState.OutgoingProgress:
+ case CallState.OutgoingRinging:
+
+ HangOut.IsEnabled = true;
+ break;
+
+ case CallState.StreamsRunning:
+ case CallState.UpdatedByRemote:
+
+ CallInProgressGuiUpdates();
+ if (call.CurrentParams.VideoEnabled)
+ {
+ StartVideoAndUpdateGui();
+ }
+ else
+ {
+ StopVideoAndUpdateGui();
+ }
+ break;
+
+ case CallState.Error:
+ case CallState.End:
+ case CallState.Released:
+
+ IncommingCall = null;
+ EndingCallGuiUpdates();
+ VideoService.StopVideoStream();
+ break;
+ }
+ }
+
+ private void StopVideoAndUpdateGui()
+ {
+ Camera.Content = "Switch on Camera";
+ Camera.IsEnabled = true;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ VideoService.StopVideoStream();
+ }
+
+ private void StartVideoAndUpdateGui()
+ {
+ VideoGrid.Visibility = Visibility.Visible;
+ Camera.Content = "Switch off Camera";
+ VideoService.StartVideoStream(VideoSwapChainPanel, PreviewSwapChainPanel);
+ Camera.IsEnabled = true;
+ }
+
+ private void EndingCallGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = true;
+ HangOut.IsEnabled = false;
+ Sound.IsEnabled = false;
+ Camera.IsEnabled = false;
+ Mic.IsEnabled = false;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ Camera.Content = "Switch on Camera";
+ Mic.Content = "Mute";
+ Sound.Content = "Switch off Sound";
+ }
+
+ private void CallInProgressGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = false;
+ HangOut.IsEnabled = true;
+ Sound.IsEnabled = true;
+ Camera.IsEnabled = true;
+ Mic.IsEnabled = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/ChatPage.xaml b/uwp/cs/06_GroupChat/Views/ChatPage.xaml
new file mode 100644
index 0000000..1964ec8
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/ChatPage.xaml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/ChatPage.xaml.cs b/uwp/cs/06_GroupChat/Views/ChatPage.xaml.cs
new file mode 100644
index 0000000..8e22a33
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/ChatPage.xaml.cs
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _06_GroupChat.Controls;
+using _06_GroupChat.Service;
+using Linphone;
+using System;
+using Windows.Storage;
+using Windows.Storage.Pickers;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _06_GroupChat.Views
+{
+ public sealed partial class ChatPage : Page
+ {
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private ChatRoom ChatRoom;
+
+ public ChatPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ ChatRoom = ((ChatRoom)e.Parameter);
+
+ ChatRoom.Listener.OnMessageReceived += OnMessageReceived;
+
+ // Here we register to almost all the different events that can appear on a conference.
+ // We use the same method AddEvent to handle all of them.
+ ChatRoom.Listener.OnConferenceLeft += AddEvent;
+ ChatRoom.Listener.OnConferenceJoined += AddEvent;
+ ChatRoom.Listener.OnParticipantDeviceRemoved += AddEvent;
+ ChatRoom.Listener.OnSubjectChanged += AddEvent;
+ ChatRoom.Listener.OnParticipantRemoved += AddEvent;
+ ChatRoom.Listener.OnParticipantAdminStatusChanged += AddEvent;
+ ChatRoom.Listener.OnParticipantAdded += AddEvent;
+
+ // If the peer address is null it means we are not in a basic
+ // ChatRoom, and that we have to wait the answer from the
+ // conference server.
+ if (ChatRoom.PeerAddress != null)
+ {
+ UpdateGUI();
+ }
+ else
+ {
+ // So we register to the OnConferenceJoined to be notified and update
+ // the frame when the ChatRoom is ready.
+ ChatHeaderText.Text = "Creation in progress";
+ ChatRoom.Listener.OnConferenceJoined += OnConferenceJoin;
+ }
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ ChatRoom.Listener.OnMessageReceived -= OnMessageReceived;
+ ChatRoom.Listener.OnConferenceLeft -= AddEvent;
+ ChatRoom.Listener.OnConferenceJoined -= AddEvent;
+ ChatRoom.Listener.OnParticipantDeviceRemoved -= AddEvent;
+ ChatRoom.Listener.OnSubjectChanged -= AddEvent;
+ ChatRoom.Listener.OnParticipantRemoved -= AddEvent;
+ ChatRoom.Listener.OnParticipantAdminStatusChanged -= AddEvent;
+ ChatRoom.Listener.OnParticipantAdded -= AddEvent;
+
+ ChatRoom.Listener.OnConferenceJoined -= OnConferenceJoin;
+
+ base.OnNavigatedFrom(e);
+ }
+
+ private void OnConferenceJoin(ChatRoom chatRoom, EventLog eventLog)
+ {
+ UpdateGUI();
+ }
+
+ private void UpdateGUI()
+ {
+ ChatHeaderText.Text = "Your conversation with : " + ChatRoom.PeerAddress.Username;
+
+ // From now on we are not only iterating over messages (GetHistory) but over all
+ // events : ChatRoom.GetHistoryEvents(int nb). As for GetHistory, 0 as a parameter
+ // means everything for GetHistoryEvents.
+ foreach (EventLog eventLog in ChatRoom.GetHistoryEvents(0))
+ {
+ // If the event is a message we do like before
+ if (EventLogType.ConferenceChatMessage.Equals(eventLog.Type))
+ {
+ AddMessage(eventLog.ChatMessage);
+ }
+ else
+ {
+ // And if it is an other type of event we use the same AddEvent method we used
+ // to register to the callbacks.
+ AddEvent(null, eventLog);
+ }
+ }
+
+ ChatRoom.MarkAsRead();
+
+ NavigationService.CurrentNavigationRoot.UpdateUnreadMessageCount();
+ NavigationService.CurrentChatspage.UpdateChatRooms();
+
+ PeerUsername.Text += ChatRoom.PeerAddress.Username;
+ YourUsername.Text += ChatRoom.LocalAddress.Username;
+
+ if (ChatRoom.HasCapability((int)ChatRoomCapabilities.Conference))
+ {
+ GroupChatDisplay participantsDisplay = new GroupChatDisplay(ChatRoom);
+ GroupChatDisplayBorder.Child = participantsDisplay;
+ GroupChatDisplayBorder.Visibility = Visibility.Visible;
+ }
+
+ // We don't allow the user to send multipart message in basic ChatRoom
+ if (ChatRoom.HasCapability((int)ChatRoomCapabilities.Basic))
+ {
+ SendMultipartButton.Visibility = Visibility.Collapsed;
+ }
+ }
+
+ private void OnMessageReceived(ChatRoom chatRoom, ChatMessage message)
+ {
+ if (ChatRoom != null)
+ {
+ AddMessage(message);
+ chatRoom.MarkAsRead();
+ }
+ }
+
+ private void AddMessage(ChatMessage chatMessage)
+ {
+ MessageDisplay messageDisplay = new MessageDisplay(chatMessage);
+
+ MessagesList.Children.Add(messageDisplay);
+
+ ScrollToBottom();
+ }
+
+ private void AddEvent(ChatRoom chatRoom, EventLog eventLog)
+ {
+ // Here we simply create an event display control ...
+ EventDisplay eventDisplay = new EventDisplay(eventLog);
+
+ // ... and adding it to the message list.
+ MessagesList.Children.Add(eventDisplay);
+
+ // See EventDisplay.xaml(.cs) to see how we handle events.
+
+ ScrollToBottom();
+ }
+
+ private void ScrollToBottom()
+ {
+ MessagesScroll.UpdateLayout();
+ MessagesScroll.ChangeView(1, MessagesScroll.ExtentHeight, 1);
+ }
+
+ private void OutgoingMessageButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (ChatRoom != null && OutgoingMessageText.Text != null && OutgoingMessageText.Text.Length > 0)
+ {
+ ChatMessage chatMessage = ChatRoom.CreateMessage(OutgoingMessageText.Text);
+ chatMessage.Send();
+ AddMessage(chatMessage);
+ }
+ OutgoingMessageText.Text = "";
+ }
+
+ private async void SendFileButton_Click(object sender, RoutedEventArgs e)
+ {
+ FileOpenPicker picker = new FileOpenPicker
+ {
+ ViewMode = PickerViewMode.List,
+ SuggestedStartLocation = PickerLocationId.DocumentsLibrary
+ };
+ picker.FileTypeFilter.Add("*");
+
+ StorageFile file = await picker.PickSingleFileAsync();
+ if (file != null)
+ {
+ Content content = await CoreService.CreateContentFromFile(file);
+ ChatMessage fileMessage = ChatRoom.CreateFileTransferMessage(content);
+ fileMessage.Send();
+ AddMessage(fileMessage);
+ }
+ }
+
+ private async void SendMultipartButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (ChatRoom != null && OutgoingMessageText.Text != null && OutgoingMessageText.Text.Length > 0)
+ {
+ // To create a multipart message simply create a message like we did before
+ ChatMessage multipartMessage = ChatRoom.CreateMessage(OutgoingMessageText.Text);
+
+ FileOpenPicker picker = new FileOpenPicker
+ {
+ ViewMode = PickerViewMode.List,
+ SuggestedStartLocation = PickerLocationId.DocumentsLibrary
+ };
+ picker.FileTypeFilter.Add("*");
+
+ StorageFile file = await picker.PickSingleFileAsync();
+ if (file != null)
+ {
+ Content content = await CoreService.CreateContentFromFile(file);
+
+ // And use "AddFileContent", "AddTextContent" or "AddUtf8TextContent" to add more
+ // contents to your message. It's as simple as that.
+ multipartMessage.AddFileContent(content);
+
+ multipartMessage.Send();
+ AddMessage(multipartMessage);
+ }
+ }
+ OutgoingMessageText.Text = "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/ChatsPage.xaml b/uwp/cs/06_GroupChat/Views/ChatsPage.xaml
new file mode 100644
index 0000000..2127d8a
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/ChatsPage.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/ChatsPage.xaml.cs b/uwp/cs/06_GroupChat/Views/ChatsPage.xaml.cs
new file mode 100644
index 0000000..fc4af96
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/ChatsPage.xaml.cs
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _06_GroupChat.Service;
+using _06_GroupChat.Shared;
+using Linphone;
+using System;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _06_GroupChat.Views
+{
+ public sealed partial class ChatsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+
+ public ChatsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ NavigationService.CurrentChatspage = this;
+ UpdateChatRooms();
+ CoreService.AddOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
+ CoreService.AddOnMessageSentDelegate(OnMessageReceiveOrSent);
+
+ // We registered to this callback so we can update the list with the new
+ // name when a chat room subject change.
+ CoreService.AddOnChatRoomSubjectChangedDelegate(AddOnChatRoomSubjectChanged);
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ NavigationService.CurrentChatspage = null;
+ CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
+ CoreService.RemoveOnMessageSentDelegate(OnMessageReceiveOrSent);
+ CoreService.RemoveOnChatRoomSubjectChangedDelegate(AddOnChatRoomSubjectChanged);
+ base.OnNavigatedFrom(e);
+ }
+
+ private void OnMessageReceiveOrSent(Core core, ChatRoom chatRoom, ChatMessage message) => UpdateChatRooms();
+
+ private void AddOnChatRoomSubjectChanged(Core core, ChatRoom chatRoom) => UpdateChatRooms();
+
+ public void UpdateChatRooms()
+ {
+ ChatRoom selectedChatRoom = (ChatRoom)ChatRoomsLV.SelectedItem;
+ ChatRoomsLV.Items.Clear();
+
+ foreach (ChatRoom chatRoom in CoreService.Core.ChatRooms)
+ {
+ if (chatRoom.HistoryEventsSize > 0)
+ {
+ ChatRoomsLV.Items.Add(chatRoom);
+ if (selectedChatRoom == chatRoom)
+ {
+ ChatRoomsLV.SelectedItem = chatRoom;
+ }
+ }
+ }
+ }
+
+ private void ChatRoomsLV_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ ChatRoomsLV.SelectedItem = e.ClickedItem;
+ ChatRoomFrame.Navigate(typeof(ChatPage), e.ClickedItem);
+ }
+
+ private async void NewChatRoom_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ string peerSipAddress = await Utils.InputTextDialogAsync("Enter peer sip address");
+ if (!String.IsNullOrWhiteSpace(peerSipAddress))
+ {
+ ChatRoom newChatRoom = CoreService.CreateOrGetChatRoom(peerSipAddress);
+ if (newChatRoom != null)
+ {
+ ChatRoomFrame.Navigate(typeof(ChatPage), newChatRoom);
+ }
+ else
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "ChatRoom creation error",
+ Content = "An error occurred during ChatRoom creation, check sip address validity and try again.",
+ CloseButtonText = "OK"
+ };
+
+ await noSettingsDialog.ShowAsync();
+ }
+ }
+ }
+
+ private void NewGroupChatRoom_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ // To create a new group chat room you need a list of participant and a subject.
+ // See CreateGroupChatRoom.xaml(.cs) to see how to create a group chat room.
+ ChatRoomFrame.Navigate(typeof(CreateGroupChatRoom));
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/CreateGroupChatRoom.xaml b/uwp/cs/06_GroupChat/Views/CreateGroupChatRoom.xaml
new file mode 100644
index 0000000..ee9085b
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/CreateGroupChatRoom.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/CreateGroupChatRoom.xaml.cs b/uwp/cs/06_GroupChat/Views/CreateGroupChatRoom.xaml.cs
new file mode 100644
index 0000000..bd543b3
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/CreateGroupChatRoom.xaml.cs
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _06_GroupChat.Service;
+using Linphone;
+using System;
+using System.Collections.ObjectModel;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace _06_GroupChat.Views
+{
+ public sealed partial class CreateGroupChatRoom : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private readonly ObservableCollection addresses = new ObservableCollection();
+
+ public ObservableCollection DisplayedAddresses
+ {
+ get { return this.addresses; }
+ }
+
+ public CreateGroupChatRoom()
+ {
+ this.InitializeComponent();
+ }
+
+ private async void Create_Click(object sender, RoutedEventArgs e)
+ {
+ // This page goal is to allow the allow the user to choose the subject of
+ // his group chat room and to prepare the list of participant.
+ // With this two things we can create a group chat room, see CoreService.CreateGroupChatRoom
+ // to learn how to create it !
+ ChatRoom newChatRoom = CoreService.CreateGroupChatRoom(DisplayedAddresses, Subject.Text);
+ if (newChatRoom != null)
+ {
+ this.Frame.Navigate(typeof(ChatPage), newChatRoom);
+ }
+ else
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "ChatRoom creation error",
+ Content = "An error occurred during group ChatRoom creation, check sip addresses validity and try again.",
+ CloseButtonText = "OK"
+ };
+
+ await noSettingsDialog.ShowAsync();
+ }
+ }
+
+ private void AddAddress_Click(object sender, RoutedEventArgs e)
+ {
+ if (!String.IsNullOrWhiteSpace(Address.Text))
+ {
+ DisplayedAddresses.Add(CoreService.Core.InterpretUrl(Address.Text));
+ AddressesLV.ItemsSource = DisplayedAddresses;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/LoginPage.xaml b/uwp/cs/06_GroupChat/Views/LoginPage.xaml
new file mode 100644
index 0000000..0a24e9f
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/LoginPage.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/LoginPage.xaml.cs b/uwp/cs/06_GroupChat/Views/LoginPage.xaml.cs
new file mode 100644
index 0000000..0280624
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/LoginPage.xaml.cs
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _06_GroupChat.Service;
+using Linphone;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _06_GroupChat.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class LoginPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public LoginPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ CoreService.AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ }
+
+ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+ {
+ CoreService.RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ base.OnNavigatingFrom(e);
+ }
+
+ ///
+ /// Called when you click on the "Login" button.
+ ///
+ private void LogInClick(object sender, RoutedEventArgs e)
+ {
+ if (LogIn.IsEnabled)
+ {
+ LogIn.IsEnabled = false;
+
+ CoreService.LogIn(Identity.Text, Password.Password);
+ }
+ }
+
+ ///
+ /// Called when a key is pressed and released on the login page.
+ /// If you pressed "Enter", simulate a login click.
+ ///
+ private void GridKeyUp(object sender, KeyRoutedEventArgs e)
+ {
+ if (VirtualKey.Enter.Equals(e.Key))
+ {
+ LogInClick(null, null);
+ }
+ }
+
+ ///
+ /// This method is called every time the RegistrationState is updated by background core's actions.
+ /// In this example we use this to update the GUI.
+ ///
+ private void OnAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, string message)
+ {
+ RegistrationText.Text = "Your registration state is : " + state.ToString();
+ switch (state)
+ {
+ case RegistrationState.Cleared:
+ case RegistrationState.None:
+ CoreService.ClearCoreAfterLogOut();
+ LogIn.IsEnabled = true;
+ break;
+
+ case RegistrationState.Ok:
+ LogIn.IsEnabled = false;
+ this.Frame.Navigate(typeof(NavigationRoot));
+ break;
+
+ case RegistrationState.Progress:
+ LogIn.IsEnabled = false;
+ break;
+
+ case RegistrationState.Failed:
+ LogIn.IsEnabled = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/NavigationRoot.xaml b/uwp/cs/06_GroupChat/Views/NavigationRoot.xaml
new file mode 100644
index 0000000..37fc841
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/NavigationRoot.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/06_GroupChat/Views/NavigationRoot.xaml.cs b/uwp/cs/06_GroupChat/Views/NavigationRoot.xaml.cs
new file mode 100644
index 0000000..289aaf6
--- /dev/null
+++ b/uwp/cs/06_GroupChat/Views/NavigationRoot.xaml.cs
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _06_GroupChat.Service;
+using Linphone;
+using System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _06_GroupChat.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class NavigationRoot : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+ private bool hasLoadedPreviously;
+
+ public NavigationRoot()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ this.CoreService.AddOnOnMessageReceivedDelegate(OnMessageReveive);
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ this.CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReveive);
+ base.OnNavigatedFrom(e);
+ }
+
+ private void Page_Loaded(object sender, RoutedEventArgs e)
+ {
+ // Only do an inital navigate the first time the page loads
+ // when we switch out of compactoverloadmode this will fire but we don't want to navigate because
+ // there is already a page loaded
+ if (!hasLoadedPreviously)
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ UpdateUnreadMessageCount();
+ hasLoadedPreviously = true;
+ NavigationService.CurrentNavigationRoot = this;
+ }
+ }
+
+ private void AppNavFrame_Navigated(object sender, NavigationEventArgs e)
+ {
+ switch (e.SourcePageType)
+ {
+ case Type c when e.SourcePageType == typeof(CallsPage):
+ ((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
+ break;
+
+ case Type c when e.SourcePageType == typeof(ChatsPage):
+ ((NavigationViewItem)navview.MenuItems[1]).IsSelected = true;
+ break;
+ }
+ }
+
+ private async void Navview_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
+ {
+ if (args.IsSettingsInvoked)
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "No settings",
+ Content = "There is no settings in this little app",
+ CloseButtonText = "OK"
+ };
+
+ ContentDialogResult result = await noSettingsDialog.ShowAsync();
+ return;
+ }
+
+ string invokedItemValue = args.InvokedItem as string;
+ if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ }
+ else
+ {
+ AppNavFrame.Navigate(typeof(ChatsPage));
+ }
+ }
+
+ private void SignOut_Tapped(object sender, TappedRoutedEventArgs e)
+ {
+ DisplaySignOutDialog();
+ }
+
+ private async void DisplaySignOutDialog()
+ {
+ ContentDialog signOutDialog = new ContentDialog
+ {
+ Title = "Sign out ?",
+ Content = "All your current calls and actions will be canceled, are you sure to continue ?",
+ PrimaryButtonText = "Sign out",
+ CloseButtonText = "Cancel"
+ };
+
+ ContentDialogResult result = await signOutDialog.ShowAsync();
+
+ if (result == ContentDialogResult.Primary)
+ {
+ CoreService.Core.TerminateAllCalls();
+ CoreService.LogOut();
+
+ this.Frame.Navigate(typeof(LoginPage));
+ }
+ }
+
+ private void OnMessageReveive(Core core, ChatRoom chatRoom, ChatMessage message)
+ {
+ UpdateUnreadMessageCount();
+ }
+
+ public void UpdateUnreadMessageCount()
+ {
+ if (CoreService.Core.UnreadChatMessageCountFromActiveLocals > 0)
+ {
+ NewMessageCount.Text = "" + CoreService.Core.UnreadChatMessageCountFromActiveLocals;
+ NewMessageCountBorder.Visibility = Visibility.Visible;
+ }
+ else
+ {
+ NewMessageCountBorder.Visibility = Visibility.Collapsed;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/07_AdvancedChat.csproj b/uwp/cs/07_AdvancedChat/07_AdvancedChat.csproj
new file mode 100644
index 0000000..a1cb4db
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/07_AdvancedChat.csproj
@@ -0,0 +1,245 @@
+
+
+
+
+ Debug
+ x86
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}
+ AppContainerExe
+ Properties
+ _07_AdvancedChat
+ 07_AdvancedChat
+ en-US
+ UAP
+ 10.0.19041.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ true
+ false
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+ App.xaml
+
+
+ ContentDisplay.xaml
+
+
+ GroupChatDisplay.xaml
+
+
+ EventDisplay.xaml
+
+
+ MessageDisplay.xaml
+
+
+
+
+
+
+ CallsPage.xaml
+
+
+
+ ChatPage.xaml
+
+
+ ChatsPage.xaml
+
+
+ CreateGroupChatRoom.xaml
+
+
+ LoginPage.xaml
+
+
+
+ NavigationRoot.xaml
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ 2.1.13
+
+
+ 5.1.0-alpha.56
+
+
+ 6.2.11
+
+
+
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/App.xaml b/uwp/cs/07_AdvancedChat/App.xaml
new file mode 100644
index 0000000..db675a4
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/App.xaml
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/App.xaml.cs b/uwp/cs/07_AdvancedChat/App.xaml.cs
new file mode 100644
index 0000000..90b16fa
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/App.xaml.cs
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _07_AdvancedChat.Service;
+using _07_AdvancedChat.Views;
+using Linphone;
+using System;
+using System.Diagnostics;
+using System.Text;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _07_AdvancedChat
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Start Linphone
+ LoggingService.Instance.LogLevel = LogLevel.Debug;
+ LoggingService.Instance.Listener.OnLogMessageWritten = OnLog;
+
+ CoreService.CoreStart(Windows.ApplicationModel.Core.CoreApplication.GetCurrentView().CoreWindow.Dispatcher);
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ if (e.PrelaunchActivated == false)
+ {
+ if (rootFrame.Content == null)
+ {
+ // When the navigation stack isn't restored navigate to the first page,
+ // configuring the new page by passing required information as a navigation
+ // parameter
+ rootFrame.Navigate(typeof(LoginPage), e.Arguments);
+ }
+ // Ensure the current window is active
+ Window.Current.Activate();
+ }
+ }
+
+ private void OnLog(LoggingService logService, string domain, LogLevel lev, string message)
+ {
+ StringBuilder builder = new StringBuilder();
+ _ = builder.Append("Linphone-[").Append(lev.ToString()).Append("](").Append(domain).Append(")").Append(message);
+ Debug.WriteLine(builder.ToString());
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Assets/LockScreenLogo.scale-200.png b/uwp/cs/07_AdvancedChat/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/uwp/cs/07_AdvancedChat/Assets/LockScreenLogo.scale-200.png differ
diff --git a/uwp/cs/07_AdvancedChat/Assets/SplashScreen.scale-200.png b/uwp/cs/07_AdvancedChat/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/uwp/cs/07_AdvancedChat/Assets/SplashScreen.scale-200.png differ
diff --git a/uwp/cs/07_AdvancedChat/Assets/Square150x150Logo.scale-200.png b/uwp/cs/07_AdvancedChat/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/uwp/cs/07_AdvancedChat/Assets/Square150x150Logo.scale-200.png differ
diff --git a/uwp/cs/07_AdvancedChat/Assets/Square44x44Logo.scale-200.png b/uwp/cs/07_AdvancedChat/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/uwp/cs/07_AdvancedChat/Assets/Square44x44Logo.scale-200.png differ
diff --git a/uwp/cs/07_AdvancedChat/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/uwp/cs/07_AdvancedChat/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/uwp/cs/07_AdvancedChat/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/uwp/cs/07_AdvancedChat/Assets/StoreLogo.png b/uwp/cs/07_AdvancedChat/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/uwp/cs/07_AdvancedChat/Assets/StoreLogo.png differ
diff --git a/uwp/cs/07_AdvancedChat/Assets/Wide310x150Logo.scale-200.png b/uwp/cs/07_AdvancedChat/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/uwp/cs/07_AdvancedChat/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/uwp/cs/07_AdvancedChat/Controls/ContentDisplay.xaml b/uwp/cs/07_AdvancedChat/Controls/ContentDisplay.xaml
new file mode 100644
index 0000000..9a37af4
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Controls/ContentDisplay.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Controls/ContentDisplay.xaml.cs b/uwp/cs/07_AdvancedChat/Controls/ContentDisplay.xaml.cs
new file mode 100644
index 0000000..75398c4
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Controls/ContentDisplay.xaml.cs
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.IO;
+using Windows.Storage;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace _07_AdvancedChat.Controls
+{
+ public sealed partial class ContentDisplay : UserControl
+ {
+ private readonly ChatMessage ChatMessage;
+ private readonly Content DisplayedContent;
+
+ public ContentDisplay(Content content, ChatMessage chatMessage)
+ {
+ this.InitializeComponent();
+ DisplayedContent = content;
+ ChatMessage = chatMessage;
+ UpdateLayoutFromContent();
+ }
+
+ private void UpdateLayoutFromContent()
+ {
+ if (DisplayedContent.IsFile || DisplayedContent.IsFileTransfer)
+ {
+ TextStack.Visibility = Visibility.Collapsed;
+ FileStack.Visibility = Visibility.Visible;
+
+ FileName.Text = DisplayedContent.Name;
+ FileSize.Text = DisplayedContent.FileSize + " bits";
+
+ if (DisplayedContent.IsFile || DisplayedContent.IsFileTransfer && ChatMessage.IsOutgoing)
+ {
+ OpenFile.Visibility = Visibility.Visible;
+ Download.Visibility = Visibility.Collapsed;
+ }
+ else
+ {
+ Download.Visibility = Visibility.Visible;
+ OpenFile.Visibility = Visibility.Collapsed;
+ }
+ }
+ else if (DisplayedContent.IsText)
+ {
+ TextStack.Visibility = Visibility.Visible;
+ FileStack.Visibility = Visibility.Collapsed;
+ TextMessage.Text = DisplayedContent.Utf8Text;
+ }
+ }
+
+ private void Download_Click(object sender, RoutedEventArgs e)
+ {
+ Download.Visibility = Visibility.Collapsed;
+ FileSize.Text = "Download in progress ...";
+
+ string downloadPathFolder = ApplicationData.Current.LocalFolder.Path + @"\Downloads\";
+ Directory.CreateDirectory(downloadPathFolder);
+ DisplayedContent.FilePath = downloadPathFolder + DisplayedContent.Name;
+
+ ChatMessage.DownloadContent(DisplayedContent);
+ }
+
+ private async void OpenFile_Click(object sender, RoutedEventArgs e)
+ {
+ string filePath = DisplayedContent.FilePath;
+ string folderPath = filePath.Substring(0, filePath.LastIndexOf("\\"));
+
+ await Launcher.LaunchFolderAsync(await StorageFolder.GetFolderFromPathAsync(folderPath));
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Controls/EventDisplay.xaml b/uwp/cs/07_AdvancedChat/Controls/EventDisplay.xaml
new file mode 100644
index 0000000..92c6e1f
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Controls/EventDisplay.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Controls/EventDisplay.xaml.cs b/uwp/cs/07_AdvancedChat/Controls/EventDisplay.xaml.cs
new file mode 100644
index 0000000..6b70011
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Controls/EventDisplay.xaml.cs
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using Windows.UI.Xaml.Controls;
+
+namespace _07_AdvancedChat.Controls
+{
+ public sealed partial class EventDisplay : UserControl
+ {
+ public EventDisplay(EventLog eventLog)
+ {
+ this.InitializeComponent();
+ switch (eventLog.Type)
+ {
+ case EventLogType.ConferenceCreated:
+ EventText.Text = $"The conference {eventLog.Subject} is created";
+ break;
+
+ case EventLogType.ConferenceTerminated:
+ EventText.Text = $"The conference {eventLog.Subject} is terminated";
+ break;
+
+ case EventLogType.ConferenceCallStart:
+ EventText.Text = "Call start";
+ break;
+
+ case EventLogType.ConferenceCallEnd:
+ EventText.Text = "Call end";
+ break;
+
+ case EventLogType.ConferenceParticipantAdded:
+ EventText.Text = $"{eventLog.ParticipantAddress.Username} is added";
+ break;
+
+ case EventLogType.ConferenceParticipantRemoved:
+ EventText.Text = $"{eventLog.ParticipantAddress.Username} is removed";
+ break;
+
+ case EventLogType.ConferenceParticipantSetAdmin:
+ EventText.Text = $"{eventLog.ParticipantAddress.Username} is now admin";
+ break;
+
+ case EventLogType.ConferenceParticipantUnsetAdmin:
+ EventText.Text = $"{eventLog.ParticipantAddress.Username} admin status removed";
+ break;
+
+ case EventLogType.ConferenceSubjectChanged:
+ EventText.Text = $"The conference subject is now {eventLog.Subject}";
+ break;
+
+ // With the ephemeral mode handling new event types can appear.
+ case EventLogType.ConferenceEphemeralMessageDisabled:
+ EventText.Text = "Ephemeral message mode is disabled";
+ break;
+
+ case EventLogType.ConferenceEphemeralMessageEnabled:
+ EventText.Text = "Ephemeral message mode is enabled";
+ break;
+
+ case EventLogType.ConferenceEphemeralMessageLifetimeChanged:
+ EventText.Text = $"Ephemeral message lifetime is now {eventLog.EphemeralMessageLifetime}";
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Controls/GroupChatDisplay.xaml b/uwp/cs/07_AdvancedChat/Controls/GroupChatDisplay.xaml
new file mode 100644
index 0000000..d636def
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Controls/GroupChatDisplay.xaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+ Participants :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Controls/GroupChatDisplay.xaml.cs b/uwp/cs/07_AdvancedChat/Controls/GroupChatDisplay.xaml.cs
new file mode 100644
index 0000000..2127927
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Controls/GroupChatDisplay.xaml.cs
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _07_AdvancedChat.Service;
+using _07_AdvancedChat.Shared;
+using Linphone;
+using System;
+using System.Linq;
+using Windows.UI.Xaml.Controls;
+
+namespace _07_AdvancedChat.Controls
+{
+ public sealed partial class GroupChatDisplay : UserControl
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private readonly ChatRoom ChatRoom;
+
+ public GroupChatDisplay(ChatRoom chatRoom)
+ {
+ this.InitializeComponent();
+ ChatRoom = chatRoom;
+ }
+
+ private void GroupChatDisplay_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ UpdateList();
+ UpdateGuiFromAdminState();
+
+ ChatRoom.Listener.OnParticipantAdded += OnParticipantListUpdate;
+ ChatRoom.Listener.OnParticipantRemoved += OnParticipantListUpdate;
+ ChatRoom.Listener.OnParticipantAdminStatusChanged += OnParticipantAdminStatusChanged;
+ }
+
+ private void GroupChatDisplay_Unloaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ ChatRoom.Listener.OnParticipantAdded -= OnParticipantListUpdate;
+ ChatRoom.Listener.OnParticipantRemoved -= OnParticipantListUpdate;
+ ChatRoom.Listener.OnParticipantAdminStatusChanged -= OnParticipantAdminStatusChanged;
+ }
+
+ private void OnParticipantAdminStatusChanged(ChatRoom chatRoom, EventLog eventLog)
+ {
+ UpdateList();
+ UpdateGuiFromAdminState();
+ }
+
+ private void UpdateGuiFromAdminState()
+ {
+ AddParticipant.IsEnabled = ChatRoom.Me.IsAdmin;
+ foreach (var control in GroupChatDisplayGrid.Children.OfType())
+ {
+ control.IsEnabled = ChatRoom.Me.IsAdmin;
+ }
+ }
+
+ private void OnParticipantListUpdate(ChatRoom chatRoom, EventLog eventLog) => UpdateList();
+
+ private void UpdateList()
+ {
+ ParticipantsLV.Items.Clear();
+ foreach (Participant participant in ChatRoom.Participants)
+ {
+ if (participant.Address != null && !String.IsNullOrWhiteSpace(participant.Address.Username))
+ {
+ ParticipantsLV.Items.Add(participant);
+ }
+ }
+ }
+
+ private void Remove_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ Participant participantToRemove = (Participant)((Button)sender).Tag;
+ ChatRoom.RemoveParticipant(participantToRemove);
+ }
+
+ private async void AddParticipant_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ string peerSipAddress = await Utils.InputTextDialogAsync("Enter peer sip address");
+ Address address = CoreService.Core.InterpretUrl(peerSipAddress);
+ if (address != null)
+ {
+ ChatRoom.AddParticipant(address);
+ }
+ else
+ {
+ ContentDialog badAddressDialog = new ContentDialog
+ {
+ Title = "Adding participant failed",
+ Content = "An error occurred during address interpretation, check sip address validity and try again.",
+ CloseButtonText = "OK"
+ };
+
+ await badAddressDialog.ShowAsync();
+ }
+ }
+
+ private void AdminSwitch_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ Participant participantToUpgrade = (Participant)((Button)sender).Tag;
+ ChatRoom.SetParticipantAdminStatus(participantToUpgrade, !participantToUpgrade.IsAdmin);
+ }
+
+ private async void RenameGroupChat_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ string newName = await Utils.InputTextDialogAsync("Enter new name for group");
+ ChatRoom.Subject = newName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Controls/MessageDisplay.xaml b/uwp/cs/07_AdvancedChat/Controls/MessageDisplay.xaml
new file mode 100644
index 0000000..26f57b2
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Controls/MessageDisplay.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Controls/MessageDisplay.xaml.cs b/uwp/cs/07_AdvancedChat/Controls/MessageDisplay.xaml.cs
new file mode 100644
index 0000000..9aff549
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Controls/MessageDisplay.xaml.cs
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace _07_AdvancedChat.Controls
+{
+ public sealed partial class MessageDisplay : UserControl
+ {
+ private readonly ChatMessage ChatMessage;
+
+ private readonly DispatcherTimer Timer;
+ private int Basetime;
+
+ public MessageDisplay(ChatMessage message)
+ {
+ this.InitializeComponent();
+ ChatMessage = message;
+ UpdateLayoutFromMessage();
+ UpdateLayoutFromContents();
+
+ // Used to create a second by second count down.
+ Timer = new DispatcherTimer
+ {
+ Interval = new TimeSpan(0, 0, 1)
+ };
+ Timer.Tick += Timer_Tick;
+ }
+
+ private void MessageDisplay_Loaded(object sender, RoutedEventArgs e)
+ {
+ ChatMessage.Listener.OnMsgStateChanged += OnMessageStateChanged;
+
+ // The countdown for ephemeral lifetime start only when the message status is "Displayed".
+ // So we register to this callback to be notified when to start the countdown.
+ // See OnEphemeralMessageTimerStarted to see how we setup the countdown.
+ ChatMessage.Listener.OnEphemeralMessageTimerStarted += OnEphemeralMessageTimerStarted;
+
+ // Even if we setup a client side countdown we will be notified by the OnEphemeralMessageDeleted
+ // callback when we should destroy the message.
+ ChatMessage.Listener.OnEphemeralMessageDeleted += OnEphemeralMessageDeleted;
+ }
+
+ private void MessageDisplay_Unloaded(object sender, RoutedEventArgs e)
+ {
+ ChatMessage.Listener = null;
+ }
+
+ private void OnMessageStateChanged(ChatMessage message, ChatMessageState state)
+ {
+ MessageState.Text = "The message state is : " + state;
+
+ switch (state)
+ {
+ case ChatMessageState.FileTransferError:
+ case ChatMessageState.FileTransferDone:
+ UpdateLayoutFromContents();
+ return;
+ }
+ }
+
+ private void UpdateLayoutFromMessage()
+ {
+ MessageState.Text = "The message state is : " + ChatMessage.State;
+ ReceiveDate.Text = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(ChatMessage.Time).ToLocalTime().ToString("HH:mm");
+ SenderName.Text += ChatMessage.FromAddress.Username;
+
+ if (ChatMessage.IsOutgoing)
+ {
+ this.HorizontalAlignment = HorizontalAlignment.Right;
+ }
+ else
+ {
+ this.HorizontalAlignment = HorizontalAlignment.Left;
+ }
+ }
+
+ private void UpdateLayoutFromContents()
+ {
+ ContentsStack.Children.Clear();
+
+ foreach (Content content in ChatMessage.Contents)
+ {
+ AddContent(content);
+ }
+ }
+
+ private void AddContent(Content content)
+ {
+ if (content.IsMultipart)
+ {
+ foreach (Content innerContent in content.Parts)
+ {
+ AddContent(innerContent);
+ }
+ return;
+ }
+
+ ContentDisplay contentDisplay = new ContentDisplay(content, ChatMessage);
+ ContentsStack.Children.Add(contentDisplay);
+ }
+
+ private void OnEphemeralMessageTimerStarted(ChatMessage message)
+ {
+ // Here we create a basic timer with the windows UI library, you can
+ // just note that we can found the ephemeral lifetime of this message
+ // as a read only attribute (ChatMessage.EphemeralLifetime).
+ Basetime = ChatMessage.EphemeralLifetime;
+ EphemeralLifetime.Text = $"{Basetime.ToString()} remaining before deletion";
+
+ // See Timer_Tick
+ Timer.Start();
+ }
+
+ private void OnEphemeralMessageDeleted(ChatMessage message)
+ {
+ // When this callback is triggered we erase the message from the view.
+ // Be careful, if you communicate with different clients some can choose
+ // to keep the message displayed even if it was an ephemeral one !
+ this.Content = new TextBlock
+ {
+ Text = "deleted ephemeral message"
+ };
+ }
+
+ private void Timer_Tick(object sender, object e)
+ {
+ // Every second we update the text under the ephemeral message
+ Basetime -= 1;
+ EphemeralLifetime.Text = $"{Basetime} remaining before deletion";
+ if (Basetime == 0)
+ {
+ Timer.Stop();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Package.appxmanifest b/uwp/cs/07_AdvancedChat/Package.appxmanifest
new file mode 100644
index 0000000..f939d08
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Package.appxmanifest
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+ 07_AdvancedChat
+ Anthony
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Properties/AssemblyInfo.cs b/uwp/cs/07_AdvancedChat/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..f40e26b
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("07_AdvancedChat")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("07_AdvancedChat")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Properties/Default.rd.xml b/uwp/cs/07_AdvancedChat/Properties/Default.rd.xml
new file mode 100644
index 0000000..76edee8
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Properties/Default.rd.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Readme.md b/uwp/cs/07_AdvancedChat/Readme.md
new file mode 100644
index 0000000..80f5921
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Readme.md
@@ -0,0 +1,53 @@
+Linphone X UWP tutorial 07_AdvancedChat
+========================================
+
+You reached the last step of this tutorial ! Well done ! In this step we will show you
+how to create group chat, enable encryption and send ephemeral messages.
+
+Linphone provide end-to-end encryption to your Flexisip chat with LIME. We will learn
+in this step how to enable and use LIME to secure our ChatRoom. If you want more
+informations about LIME take a look to [this page](https://linphone.org/technical-corner/lime).
+
+To test a secure chat room try to create a group chat room and tick the check box
+"I want a secure chat room". Most of the new and documented code can be found in
+CoreService.cs.
+
+One-to-one encryption is similar to group chat encryption as it uses LIME. To learn
+how to create a one-to-one encrypted chat room also see CoreService.cs. You can also try to create one
+yourself using the new "Create a new secure ChatRoom" on top of the ChatsPage.
+
+Ephemeral messages are meant to disappear after a certain amount of time. You can enable the ephemeral
+mode directly on a ChatRoom object, see ChatPage.xaml.cs to learn how to enable/disable it. And finally
+watch the new code in MessageDisplay.xaml(.cs) to see how to handle ephemeral messages.
+
+Don't forget to install those NuGet packages :
+ - LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
+ - Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
+ - ANGLE.WindowsStore (for video rendering, version 2.1.13 recommended)
+
+New/updated files :
+
+```
+07_AdvancedChat
+└───Controls :
+│ │
+│ │ MessageDisplay.xaml(.cs) : A user control to display chat bubbles from message event.
+│ │ Ephemeral messages handling is added here in this step !
+└───Service :
+│ │ CoreService.cs : A singleton service which contains the Linphone.Core.
+│ │ Updated to allow creation of encrypted group chat room.
+│ │
+│
+└───Shared :
+│ │ ChatRoomToStringConverter.cs : a class that implement IValueConverter to display the
+│ │ the chat room name according to its type. Now we display a SECURE tag
+│ │ for secure chat room
+│ │
+└───Views :
+│ │
+│ │ ChatPage.xaml(.cs) : This is the frame displayed when you select a chat room.
+│ │ You can now enable/disable ephemeral mode here.
+│ │
+│ │ ChatsPage.xaml(.cs) : ChatRoom list with a new "Create a new secure ChatRoom" button.
+│ │
+```
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Service/CoreService.cs b/uwp/cs/07_AdvancedChat/Service/CoreService.cs
new file mode 100644
index 0000000..a5c77bc
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Service/CoreService.cs
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Linphone;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Media.Audio;
+using Windows.Media.Capture;
+using Windows.Storage;
+using Windows.UI.Core;
+using static Linphone.CoreListener;
+
+namespace _07_AdvancedChat.Service
+{
+ internal class CoreService
+ {
+ private Timer Timer;
+
+ private static readonly CoreService instance = new CoreService();
+
+ public static CoreService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private Core core;
+
+ public Core Core
+ {
+ get
+ {
+ if (core == null)
+ {
+ Factory factory = Factory.Instance;
+
+ string assetsPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share");
+ factory.TopResourcesDir = assetsPath;
+ factory.DataResourcesDir = assetsPath;
+ factory.SoundResourcesDir = Path.Combine(assetsPath, "sounds", "linphone");
+ factory.RingResourcesDir = Path.Combine(factory.SoundResourcesDir, "rings");
+ factory.ImageResourcesDir = Path.Combine(assetsPath, "images");
+ factory.MspluginsDir = ".";
+
+ core = factory.CreateCore(Path.Combine(ApplicationData.Current.LocalFolder.Path, "configuration"), "", IntPtr.Zero);
+
+ core.AudioPort = 7666;
+ core.VideoPort = 9666;
+
+ // You only need to give your LIME server URL
+ core.LimeX3DhServerUrl = "https://lime.linphone.org/lime-server/lime-server.php";
+ // and enable LIME on your core to use encryption.
+ core.LimeX3DhEnabled = true;
+ // Now see the CoreService.CreateGroupChatRoom to see how to create a secure chat room
+
+ core.RootCa = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share", "Linphone", "rootca.pem");
+ core.UserCertificatesPath = ApplicationData.Current.LocalFolder.Path;
+
+ VideoActivationPolicy videoActivationPolicy = factory.CreateVideoActivationPolicy();
+ videoActivationPolicy.AutomaticallyAccept = true;
+ videoActivationPolicy.AutomaticallyInitiate = false;
+ core.VideoActivationPolicy = videoActivationPolicy;
+
+ if (core.VideoSupported())
+ {
+ core.VideoCaptureEnabled = true;
+ }
+ core.UsePreviewWindow(true);
+
+ core.FileTransferServer = "https://www.linphone.org:444/lft.php";
+ }
+
+ return core;
+ }
+ }
+
+ public void CoreStart(CoreDispatcher dispatcher)
+ {
+ Core.Start();
+
+ Timer = new Timer(OnTimedEvent, dispatcher, 20, 20);
+ }
+
+ private async void OnTimedEvent(object state)
+ {
+ await ((CoreDispatcher)state).RunIdleAsync((args) =>
+ {
+ Core.Iterate();
+ });
+ }
+
+ public void AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged += myDelegate;
+ }
+
+ public void RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnAccountRegistrationStateChanged -= myDelegate;
+ }
+
+ public void AddOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged += myDelegate;
+ }
+
+ public void RemoveOnCallStateChangedDelegate(OnCallStateChangedDelegate myDelegate)
+ {
+ Core.Listener.OnCallStateChanged -= myDelegate;
+ }
+
+ public void AddOnOnMessageReceivedDelegate(OnMessageReceivedDelegate myDelegate)
+ {
+ Core.Listener.OnMessageReceived += myDelegate;
+ }
+
+ public void RemoveOnOnMessageReceivedDelegate(OnMessageReceivedDelegate myDelegate)
+ {
+ Core.Listener.OnMessageReceived -= myDelegate;
+ }
+
+ public void AddOnMessageSentDelegate(OnMessageSentDelegate myDelegate)
+ {
+ Core.Listener.OnMessageSent += myDelegate;
+ }
+
+ public void RemoveOnMessageSentDelegate(OnMessageSentDelegate myDelegate)
+ {
+ Core.Listener.OnMessageSent -= myDelegate;
+ }
+
+ public void AddOnChatRoomSubjectChangedDelegate(OnChatRoomSubjectChangedDelegate myDelegate)
+ {
+ Core.Listener.OnChatRoomSubjectChanged += myDelegate;
+ }
+
+ public void RemoveOnChatRoomSubjectChangedDelegate(OnChatRoomSubjectChangedDelegate myDelegate)
+ {
+ Core.Listener.OnChatRoomSubjectChanged -= myDelegate;
+ }
+
+ public void LogIn(string identity, string password)
+ {
+ Address address = Factory.Instance.CreateAddress(identity);
+ AuthInfo authInfo = Factory.Instance.CreateAuthInfo(address.Username, "", password, "", "", address.Domain);
+ Core.AddAuthInfo(authInfo);
+
+ AccountParams accountParams = Core.CreateAccountParams();
+ accountParams.IdentityAddress = address;
+ string serverAddr = "sip:" + address.Domain + ";transport=tls";
+ accountParams.ServerAddr = serverAddr;
+
+ accountParams.RegisterEnabled = true;
+
+ // If you want to create some group chats (conferences) you need to
+ // specify a conference factory URI. Here is the Linphone.org conference
+ // factory URI.
+ accountParams.ConferenceFactoryUri = "sip:conference-factory@sip.linphone.org";
+
+ Account account = Core.CreateAccount(accountParams);
+ Core.AddAccount(account);
+ Core.DefaultAccount = account;
+ }
+
+ public void LogOut()
+ {
+ Account account = Core.DefaultAccount;
+ if (account != null)
+ {
+ AccountParams accountParams = account.Params.Clone();
+ accountParams.RegisterEnabled = false;
+ account.Params = accountParams;
+ }
+ }
+
+ public void ClearCoreAfterLogOut()
+ {
+ Core.ClearAllAuthInfo();
+ Core.ClearAccounts();
+ }
+
+ public async void Call(string uriToCall)
+ {
+ await OpenMicrophonePopup();
+
+ Address address = Core.InterpretUrl(uriToCall);
+ Core.InviteAddress(address);
+ }
+
+ public bool MicEnabledSwitch()
+ {
+ return Core.MicEnabled = !Core.MicEnabled;
+ }
+
+ public bool SpeakerMutedSwitch()
+ {
+ return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
+ }
+
+ public async Task CameraEnabledSwitchAsync()
+ {
+ await OpenCameraPopup();
+
+ Call call = Core.CurrentCall;
+ CallParams param = core.CreateCallParams(call);
+ bool newValue = !param.VideoEnabled;
+ param.VideoEnabled = newValue;
+ call.Update(param);
+
+ return newValue;
+ }
+
+ public ChatRoom CreateOrGetChatRoom(string sipAddress, bool isSecure)
+ {
+ Address remoteAddress = Core.InterpretUrl(sipAddress);
+ Address localAdress = Core.DefaultProxyConfig.IdentityAddress;
+
+ ChatRoomParams chatRoomParams = Core.CreateDefaultChatRoomParams();
+ // To create a one-to-one encrypted chat room we still put GroupEnabled to false.
+ chatRoomParams.GroupEnabled = false;
+ chatRoomParams.RttEnabled = false;
+
+ if (isSecure)
+ {
+ // But here are the things that differ from a basic chat room.
+ // You must use a Flexisip backend,
+ chatRoomParams.Backend = ChatRoomBackend.FlexisipChat;
+
+ // enable encryption and choose your type of encryption backend,
+ chatRoomParams.EncryptionBackend = ChatRoomEncryptionBackend.Lime;
+ chatRoomParams.EncryptionEnabled = isSecure;
+
+ // and you must set a subject. But often for one-to-one chat rooms the client
+ // don't use or display the subject (see ChatRoomToStringConverter), so you can
+ // put a hard coded subject.
+ chatRoomParams.Subject = "Dummy Subject";
+ }
+ else
+ {
+ chatRoomParams.Backend = ChatRoomBackend.Basic;
+ chatRoomParams.EncryptionBackend = ChatRoomEncryptionBackend.None;
+ chatRoomParams.EncryptionEnabled = isSecure;
+ }
+
+ return Core.CreateChatRoom(chatRoomParams, localAdress, new[] { remoteAddress });
+ }
+
+ public ChatRoom CreateGroupChatRoom(IEnumerable participants, string subject, bool isSecure)
+ {
+ Address localAdress = Core.DefaultProxyConfig.IdentityAddress;
+
+ ChatRoomParams chatRoomParams = Core.CreateDefaultChatRoomParams();
+ chatRoomParams.Backend = ChatRoomBackend.FlexisipChat;
+ chatRoomParams.GroupEnabled = true;
+ chatRoomParams.RttEnabled = false;
+ chatRoomParams.Subject = subject;
+
+ if (isSecure)
+ {
+ // If you want to use encryption you should be in a chat room with a Flexisip
+ // backend, this why we offer the option only on the group chat room for now.
+ // Then simply choose your encryption backend (only LIME is available by default)
+ chatRoomParams.EncryptionBackend = ChatRoomEncryptionBackend.Lime;
+ // and put EncryptionEnabled to true.
+ chatRoomParams.EncryptionEnabled = isSecure;
+
+ // Simply doing this you have now a group chat room with end-to-end encryption.
+ }
+ else
+ {
+ chatRoomParams.EncryptionBackend = ChatRoomEncryptionBackend.None;
+ chatRoomParams.EncryptionEnabled = isSecure;
+ }
+
+ return Core.CreateChatRoom(chatRoomParams, localAdress, participants);
+ }
+
+ public async Task CreateContentFromFile(StorageFile file)
+ {
+ StorageFile fileCopy = await file.CopyAsync(ApplicationData.Current.LocalFolder, file.Name, NameCollisionOption.ReplaceExisting);
+
+ Content content = Core.CreateContent();
+ content.FilePath = fileCopy.Path;
+
+ string[] splittedMimeType = fileCopy.ContentType.Split("/");
+ content.Type = splittedMimeType[0];
+ content.Subtype = splittedMimeType[1];
+
+ // Set the file name for the receiver
+ content.Name = fileCopy.Name;
+
+ return content;
+ }
+
+ private async Task OpenMicrophonePopup()
+ {
+ AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
+ CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
+ AudioGraph audioGraph = result.Graph;
+
+ CreateAudioDeviceInputNodeResult resultNode = await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media);
+ AudioDeviceInputNode deviceInputNode = resultNode.DeviceInputNode;
+
+ deviceInputNode.Dispose();
+ audioGraph.Dispose();
+ }
+
+ private async Task OpenCameraPopup()
+ {
+ MediaCapture mediaCapture = new Windows.Media.Capture.MediaCapture();
+ await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
+ {
+ StreamingCaptureMode = StreamingCaptureMode.Video
+ });
+ mediaCapture.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Service/NavigationService.cs b/uwp/cs/07_AdvancedChat/Service/NavigationService.cs
new file mode 100644
index 0000000..f8932ea
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Service/NavigationService.cs
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _07_AdvancedChat.Views;
+
+namespace _07_AdvancedChat.Service
+{
+ internal class NavigationService
+ {
+ private static readonly NavigationService instance = new NavigationService();
+
+ public static NavigationService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ public NavigationRoot CurrentNavigationRoot { get; set; }
+
+ public ChatsPage CurrentChatspage { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Service/VideoService.cs b/uwp/cs/07_AdvancedChat/Service/VideoService.cs
new file mode 100644
index 0000000..b6e63f6
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Service/VideoService.cs
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using Windows.UI.Xaml.Controls;
+
+namespace _07_AdvancedChat.Service
+{
+ internal class VideoService
+ {
+ private static readonly VideoService instance = new VideoService();
+
+ public static VideoService Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public void StartVideoStream(SwapChainPanel main, SwapChainPanel preview)
+ {
+ CoreService.Core.NativePreviewWindowId = preview;
+ CoreService.Core.NativeVideoWindowId = main;
+ }
+
+ public void StopVideoStream()
+ {
+ CoreService.Core.NativePreviewWindowId = null;
+ CoreService.Core.NativeVideoWindowId = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Shared/ChatRoomToStringConverter.cs b/uwp/cs/07_AdvancedChat/Shared/ChatRoomToStringConverter.cs
new file mode 100644
index 0000000..11764cf
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Shared/ChatRoomToStringConverter.cs
@@ -0,0 +1,47 @@
+using Linphone;
+using System;
+using System.Linq;
+using Windows.UI.Xaml.Data;
+
+namespace _07_AdvancedChat.Shared
+{
+ public class ChatRoomToStringConverter : IValueConverter
+ {
+ object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
+ {
+ ChatRoom chatRoom = (ChatRoom)value;
+ string nameInList = null;
+ if (chatRoom.HasCapability((int)ChatRoomCapabilities.Basic))
+ {
+ nameInList = chatRoom.PeerAddress.Username;
+ }
+ else if (chatRoom.HasCapability((int)ChatRoomCapabilities.OneToOne))
+ {
+ nameInList = chatRoom.Participants.FirstOrDefault() == null ? "" : chatRoom.Participants.First().Address.Username;
+ }
+ else if (chatRoom.HasCapability((int)ChatRoomCapabilities.Conference))
+ {
+ nameInList = chatRoom.Subject;
+ }
+
+ // You can check the Encrypted ChatRoomCapabilities to know if a ChatRoom uses
+ // encryption or not.
+ if (chatRoom.HasCapability((int)ChatRoomCapabilities.Encrypted))
+ {
+ nameInList += " #SECURE#";
+ }
+
+ if (String.IsNullOrEmpty(nameInList))
+ {
+ nameInList = "Incoherent ChatRoom values";
+ }
+
+ return nameInList;
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Shared/Utils.cs b/uwp/cs/07_AdvancedChat/Shared/Utils.cs
new file mode 100644
index 0000000..28b8f37
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Shared/Utils.cs
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.Threading.Tasks;
+using Windows.UI.Xaml.Controls;
+
+namespace _07_AdvancedChat.Shared
+{
+ public class Utils
+ {
+ public static async Task InputTextDialogAsync(string title)
+ {
+ TextBox inputTextBox = new TextBox
+ {
+ AcceptsReturn = false,
+ Height = 32
+ };
+ ContentDialog dialog = new ContentDialog
+ {
+ Content = inputTextBox,
+ Title = title,
+ IsSecondaryButtonEnabled = true,
+ PrimaryButtonText = "OK",
+ SecondaryButtonText = "Cancel"
+ };
+ if (await dialog.ShowAsync() == ContentDialogResult.Primary)
+ return inputTextBox.Text;
+ else
+ return "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/CallsPage.xaml b/uwp/cs/07_AdvancedChat/Views/CallsPage.xaml
new file mode 100644
index 0000000..991f7f5
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/CallsPage.xaml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/CallsPage.xaml.cs b/uwp/cs/07_AdvancedChat/Views/CallsPage.xaml.cs
new file mode 100644
index 0000000..d6d4195
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/CallsPage.xaml.cs
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _07_AdvancedChat.Service;
+using Linphone;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _07_AdvancedChat.Views
+{
+ public sealed partial class CallsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private VideoService VideoService { get; } = VideoService.Instance;
+
+ private Call IncommingCall;
+
+ public CallsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ VideoService.StopVideoStream();
+ CoreService.RemoveOnCallStateChangedDelegate(OnCallStateChanged);
+ base.OnNavigatedFrom(e);
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ HelloText.Text += CoreService.Core.DefaultProxyConfig.FindAuthInfo().Username;
+ CoreService.AddOnCallStateChangedDelegate(OnCallStateChanged);
+
+ if (CoreService.Core.CurrentCall != null)
+ {
+ OnCallStateChanged(CoreService.Core, CoreService.Core.CurrentCall, CoreService.Core.CurrentCall.State, null);
+ }
+ }
+
+ private void CallClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Call(UriToCall.Text);
+ }
+
+ private void HangOutClick(object sender, RoutedEventArgs e)
+ {
+ CoreService.Core.TerminateAllCalls();
+ }
+
+ private void SoundClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.SpeakerMutedSwitch())
+ {
+ Sound.Content = "Switch on Sound";
+ }
+ else
+ {
+ Sound.Content = "Switch off Sound";
+ }
+ }
+
+ private async void CameraClick(object sender, RoutedEventArgs e)
+ {
+ await CoreService.CameraEnabledSwitchAsync();
+ Camera.Content = "Waiting for accept ...";
+ Camera.IsEnabled = false;
+ }
+
+ private void MicClick(object sender, RoutedEventArgs e)
+ {
+ if (CoreService.MicEnabledSwitch())
+ {
+ Mic.Content = "Mute";
+ }
+ else
+ {
+ Mic.Content = "Unmute";
+ }
+ }
+
+ private void AnswerClick(object sender, RoutedEventArgs e)
+ {
+ IncommingCall.Accept();
+ IncommingCall = null;
+ }
+
+ private void DeclineClick(object sender, RoutedEventArgs e)
+ {
+ if (IncommingCall != null)
+ {
+ IncommingCall.Decline(Reason.Declined);
+ IncommingCall = null;
+ }
+ }
+
+ private void OnCallStateChanged(Core core, Call call, CallState state, string message)
+ {
+ CallText.Text = "Your call state is : " + state.ToString();
+ switch (state)
+ {
+ case CallState.IncomingReceived:
+
+ IncommingCall = call;
+ IncomingCallStackPanel.Visibility = Visibility.Visible;
+ IncommingCallText.Text = " " + call.RemoteAddress.AsString();
+ break;
+
+ case CallState.OutgoingInit:
+ case CallState.OutgoingProgress:
+ case CallState.OutgoingRinging:
+
+ HangOut.IsEnabled = true;
+ break;
+
+ case CallState.StreamsRunning:
+ case CallState.UpdatedByRemote:
+
+ CallInProgressGuiUpdates();
+ if (call.CurrentParams.VideoEnabled)
+ {
+ StartVideoAndUpdateGui();
+ }
+ else
+ {
+ StopVideoAndUpdateGui();
+ }
+ break;
+
+ case CallState.Error:
+ case CallState.End:
+ case CallState.Released:
+
+ IncommingCall = null;
+ EndingCallGuiUpdates();
+ VideoService.StopVideoStream();
+ break;
+ }
+ }
+
+ private void StopVideoAndUpdateGui()
+ {
+ Camera.Content = "Switch on Camera";
+ Camera.IsEnabled = true;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ VideoService.StopVideoStream();
+ }
+
+ private void StartVideoAndUpdateGui()
+ {
+ VideoGrid.Visibility = Visibility.Visible;
+ Camera.Content = "Switch off Camera";
+ VideoService.StartVideoStream(VideoSwapChainPanel, PreviewSwapChainPanel);
+ Camera.IsEnabled = true;
+ }
+
+ private void EndingCallGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = true;
+ HangOut.IsEnabled = false;
+ Sound.IsEnabled = false;
+ Camera.IsEnabled = false;
+ Mic.IsEnabled = false;
+ VideoGrid.Visibility = Visibility.Collapsed;
+ Camera.Content = "Switch on Camera";
+ Mic.Content = "Mute";
+ Sound.Content = "Switch off Sound";
+ }
+
+ private void CallInProgressGuiUpdates()
+ {
+ IncomingCallStackPanel.Visibility = Visibility.Collapsed;
+ CallButton.IsEnabled = false;
+ HangOut.IsEnabled = true;
+ Sound.IsEnabled = true;
+ Camera.IsEnabled = true;
+ Mic.IsEnabled = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/ChatPage.xaml b/uwp/cs/07_AdvancedChat/Views/ChatPage.xaml
new file mode 100644
index 0000000..9a5c447
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/ChatPage.xaml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/ChatPage.xaml.cs b/uwp/cs/07_AdvancedChat/Views/ChatPage.xaml.cs
new file mode 100644
index 0000000..7a0d56f
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/ChatPage.xaml.cs
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _07_AdvancedChat.Controls;
+using _07_AdvancedChat.Service;
+using Linphone;
+using System;
+using Windows.Storage;
+using Windows.Storage.Pickers;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _07_AdvancedChat.Views
+{
+ public sealed partial class ChatPage : Page
+ {
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private ChatRoom ChatRoom;
+
+ public ChatPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ ChatRoom = ((ChatRoom)e.Parameter);
+
+ ChatRoom.Listener.OnMessageReceived += OnMessageReceived;
+ ChatRoom.Listener.OnConferenceLeft += AddEvent;
+ ChatRoom.Listener.OnConferenceJoined += AddEvent;
+ ChatRoom.Listener.OnParticipantDeviceRemoved += AddEvent;
+ ChatRoom.Listener.OnSubjectChanged += AddEvent;
+ ChatRoom.Listener.OnParticipantRemoved += AddEvent;
+ ChatRoom.Listener.OnParticipantAdminStatusChanged += AddEvent;
+ ChatRoom.Listener.OnParticipantAdded += AddEvent;
+ ChatRoom.Listener.OnEphemeralEvent += AddEvent;
+
+ // In this step we want to test the ephemeral messages. This feature is only
+ // available if you are using in a Flexisip backend.
+ if (ChatRoomBackend.FlexisipChat.Equals(ChatRoom.CurrentParams.Backend) && !ChatRoom.EphemeralEnabled)
+ {
+ // In this step when the ephemeral feature is available we enable it
+ // by default. Just set the EphemeralEnabled to true to activate the
+ // ephemeral mode, after this point every message send will be marked
+ // as ephemeral
+ ChatRoom.EphemeralEnabled = true;
+
+ // You can choose how many times you want the message to be displayed before
+ // getting destroyed with EphemeralLifetime, the value is in second.
+ // Here we set a low value for testing purpose.
+ ChatRoom.EphemeralLifetime = 15;
+
+ // At any time you can choose to disable the ephemeral mode moving EphemeralEnabled to false.
+ // See the EphemeralCheckBox_Unchecked method.
+ }
+ else if (!ChatRoomBackend.FlexisipChat.Equals(ChatRoom.CurrentParams.Backend))
+ {
+ // If the backend is not a Flexisip one we hide the ephemeral feature in the ChatPage
+ // because it doesn't support it.
+ EphemeralCheckBox.Visibility = Visibility.Collapsed;
+ }
+
+ if (ChatRoom.PeerAddress != null)
+ {
+ UpdateGUI();
+ }
+ else
+ {
+ ChatHeaderText.Text = "Creation in progress";
+ ChatRoom.Listener.OnConferenceJoined += OnConferenceJoin;
+ }
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ ChatRoom.Listener.OnMessageReceived -= OnMessageReceived;
+ ChatRoom.Listener.OnConferenceLeft -= AddEvent;
+ ChatRoom.Listener.OnConferenceJoined -= AddEvent;
+ ChatRoom.Listener.OnParticipantDeviceRemoved -= AddEvent;
+ ChatRoom.Listener.OnSubjectChanged -= AddEvent;
+ ChatRoom.Listener.OnParticipantRemoved -= AddEvent;
+ ChatRoom.Listener.OnParticipantAdminStatusChanged -= AddEvent;
+ ChatRoom.Listener.OnParticipantAdded -= AddEvent;
+
+ ChatRoom.Listener.OnConferenceJoined -= OnConferenceJoin;
+
+ base.OnNavigatedFrom(e);
+ }
+
+ private void OnConferenceJoin(ChatRoom chatRoom, EventLog eventLog)
+ {
+ UpdateGUI();
+ }
+
+ private void UpdateGUI()
+ {
+ ChatHeaderText.Text = "Your conversation with : " + ChatRoom.PeerAddress.Username;
+ foreach (EventLog eventLog in ChatRoom.GetHistoryEvents(0))
+ {
+ if (EventLogType.ConferenceChatMessage.Equals(eventLog.Type))
+ {
+ AddMessage(eventLog.ChatMessage);
+ }
+ else
+ {
+ AddEvent(null, eventLog);
+ }
+ }
+
+ ChatRoom.MarkAsRead();
+
+ NavigationService.CurrentNavigationRoot.UpdateUnreadMessageCount();
+ NavigationService.CurrentChatspage.UpdateChatRooms();
+
+ PeerUsername.Text += ChatRoom.PeerAddress.Username;
+ YourUsername.Text += ChatRoom.LocalAddress.Username;
+
+ // We only display the GroupChatDisplay if we are in real group chat,
+ // we don't if it's a OneToOne secure ChatRoom.
+ if (ChatRoom.HasCapability((int)ChatRoomCapabilities.Conference)
+ && !ChatRoom.HasCapability((int)ChatRoomCapabilities.OneToOne))
+ {
+ GroupChatDisplay participantsDisplay = new GroupChatDisplay(ChatRoom);
+ GroupChatDisplayBorder.Child = participantsDisplay;
+ GroupChatDisplayBorder.Visibility = Visibility.Visible;
+ }
+
+ if (ChatRoom.HasCapability((int)ChatRoomCapabilities.Basic))
+ {
+ SendMultipartButton.Visibility = Visibility.Collapsed;
+ }
+ }
+
+ private void OnMessageReceived(ChatRoom chatRoom, ChatMessage message)
+ {
+ if (ChatRoom != null)
+ {
+ AddMessage(message);
+ chatRoom.MarkAsRead();
+ }
+ }
+
+ private void AddMessage(ChatMessage chatMessage)
+ {
+ MessageDisplay messageDisplay = new MessageDisplay(chatMessage);
+
+ MessagesList.Children.Add(messageDisplay);
+
+ ScrollToBottom();
+ }
+
+ private void AddEvent(ChatRoom chatRoom, EventLog eventLog)
+ {
+ EventDisplay eventDisplay = new EventDisplay(eventLog);
+
+ MessagesList.Children.Add(eventDisplay);
+
+ ScrollToBottom();
+ }
+
+ private void ScrollToBottom()
+ {
+ MessagesScroll.UpdateLayout();
+ MessagesScroll.ChangeView(1, MessagesScroll.ExtentHeight, 1);
+ }
+
+ private void OutgoingMessageButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (ChatRoom != null && OutgoingMessageText.Text != null && OutgoingMessageText.Text.Length > 0)
+ {
+ ChatMessage chatMessage = ChatRoom.CreateMessage(OutgoingMessageText.Text);
+ chatMessage.Send();
+ AddMessage(chatMessage);
+ }
+ OutgoingMessageText.Text = "";
+ }
+
+ private async void SendFileButton_Click(object sender, RoutedEventArgs e)
+ {
+ FileOpenPicker picker = new FileOpenPicker
+ {
+ ViewMode = PickerViewMode.List,
+ SuggestedStartLocation = PickerLocationId.DocumentsLibrary
+ };
+ picker.FileTypeFilter.Add("*");
+
+ StorageFile file = await picker.PickSingleFileAsync();
+ if (file != null)
+ {
+ Content content = await CoreService.CreateContentFromFile(file);
+ ChatMessage fileMessage = ChatRoom.CreateFileTransferMessage(content);
+ fileMessage.Send();
+ AddMessage(fileMessage);
+ }
+ }
+
+ private async void SendMultipartButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (ChatRoom != null && OutgoingMessageText.Text != null && OutgoingMessageText.Text.Length > 0)
+ {
+ ChatMessage multipartMessage = ChatRoom.CreateMessage(OutgoingMessageText.Text);
+
+ FileOpenPicker picker = new FileOpenPicker
+ {
+ ViewMode = PickerViewMode.List,
+ SuggestedStartLocation = PickerLocationId.DocumentsLibrary
+ };
+ picker.FileTypeFilter.Add("*");
+
+ StorageFile file = await picker.PickSingleFileAsync();
+ if (file != null)
+ {
+ Content content = await CoreService.CreateContentFromFile(file);
+ multipartMessage.AddFileContent(content);
+
+ multipartMessage.Send();
+ AddMessage(multipartMessage);
+ }
+ }
+ OutgoingMessageText.Text = "";
+ }
+
+ private void EphemeralCheckBox_Checked(object sender, RoutedEventArgs e)
+ {
+ if (ChatRoom != null)
+ {
+ ChatRoom.EphemeralEnabled = true;
+ }
+ }
+
+ private void EphemeralCheckBox_Unchecked(object sender, RoutedEventArgs e)
+ {
+ if (ChatRoom != null)
+ {
+ ChatRoom.EphemeralEnabled = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/ChatsPage.xaml b/uwp/cs/07_AdvancedChat/Views/ChatsPage.xaml
new file mode 100644
index 0000000..efadcad
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/ChatsPage.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/ChatsPage.xaml.cs b/uwp/cs/07_AdvancedChat/Views/ChatsPage.xaml.cs
new file mode 100644
index 0000000..0bf1d7a
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/ChatsPage.xaml.cs
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _07_AdvancedChat.Service;
+using _07_AdvancedChat.Shared;
+using Linphone;
+using System;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace _07_AdvancedChat.Views
+{
+ public sealed partial class ChatsPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+
+ public ChatsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ NavigationService.CurrentChatspage = this;
+ UpdateChatRooms();
+ CoreService.AddOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
+ CoreService.AddOnMessageSentDelegate(OnMessageReceiveOrSent);
+ CoreService.AddOnChatRoomSubjectChangedDelegate(AddOnChatRoomSubjectChanged);
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ NavigationService.CurrentChatspage = null;
+ CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
+ CoreService.RemoveOnMessageSentDelegate(OnMessageReceiveOrSent);
+ CoreService.RemoveOnChatRoomSubjectChangedDelegate(AddOnChatRoomSubjectChanged);
+ base.OnNavigatedFrom(e);
+ }
+
+ private void OnMessageReceiveOrSent(Core core, ChatRoom chatRoom, ChatMessage message) => UpdateChatRooms();
+
+ private void AddOnChatRoomSubjectChanged(Core core, ChatRoom chatRoom) => UpdateChatRooms();
+
+ public void UpdateChatRooms()
+ {
+ ChatRoom selectedChatRoom = (ChatRoom)ChatRoomsLV.SelectedItem;
+ ChatRoomsLV.Items.Clear();
+
+ foreach (ChatRoom chatRoom in CoreService.Core.ChatRooms)
+ {
+ if (chatRoom.HistoryEventsSize > 0)
+ {
+ ChatRoomsLV.Items.Add(chatRoom);
+ if (selectedChatRoom == chatRoom)
+ {
+ ChatRoomsLV.SelectedItem = chatRoom;
+ }
+ }
+ }
+ }
+
+ private void ChatRoomsLV_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ ChatRoomsLV.SelectedItem = e.ClickedItem;
+ ChatRoomFrame.Navigate(typeof(ChatPage), e.ClickedItem);
+ }
+
+ private async void NewChatRoom_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ string peerSipAddress = await Utils.InputTextDialogAsync("Enter peer sip address");
+ bool secure = Boolean.Parse((string)((Button)sender).Tag);
+ if (!String.IsNullOrWhiteSpace(peerSipAddress))
+ {
+ ChatRoom newChatRoom = CoreService.CreateOrGetChatRoom(peerSipAddress, secure);
+ if (newChatRoom != null)
+ {
+ ChatRoomFrame.Navigate(typeof(ChatPage), newChatRoom);
+ }
+ else
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "ChatRoom creation error",
+ Content = "An error occurred during ChatRoom creation, check sip address validity and try again.",
+ CloseButtonText = "OK"
+ };
+
+ await noSettingsDialog.ShowAsync();
+ }
+ }
+ }
+
+ private void NewGroupChatRoom_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ {
+ ChatRoomFrame.Navigate(typeof(CreateGroupChatRoom));
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/CreateGroupChatRoom.xaml b/uwp/cs/07_AdvancedChat/Views/CreateGroupChatRoom.xaml
new file mode 100644
index 0000000..7beb37f
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/CreateGroupChatRoom.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/CreateGroupChatRoom.xaml.cs b/uwp/cs/07_AdvancedChat/Views/CreateGroupChatRoom.xaml.cs
new file mode 100644
index 0000000..d878c67
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/CreateGroupChatRoom.xaml.cs
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _07_AdvancedChat.Service;
+using Linphone;
+using System;
+using System.Collections.ObjectModel;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace _07_AdvancedChat.Views
+{
+ public sealed partial class CreateGroupChatRoom : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ private readonly ObservableCollection addresses = new ObservableCollection();
+
+ public ObservableCollection DisplayedAddresses
+ {
+ get { return this.addresses; }
+ }
+
+ public CreateGroupChatRoom()
+ {
+ this.InitializeComponent();
+ }
+
+ private async void Create_Click(object sender, RoutedEventArgs e)
+ {
+ ChatRoom newChatRoom = CoreService.CreateGroupChatRoom(DisplayedAddresses, Subject.Text, SecureCheckBox.IsChecked ?? false);
+ if (newChatRoom != null)
+ {
+ this.Frame.Navigate(typeof(ChatPage), newChatRoom);
+ }
+ else
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "ChatRoom creation error",
+ Content = "An error occurred during group ChatRoom creation, check sip addresses validity and try again.",
+ CloseButtonText = "OK"
+ };
+
+ await noSettingsDialog.ShowAsync();
+ }
+ }
+
+ private void AddAddress_Click(object sender, RoutedEventArgs e)
+ {
+ if (!String.IsNullOrWhiteSpace(Address.Text))
+ {
+ DisplayedAddresses.Add(CoreService.Core.InterpretUrl(Address.Text));
+ AddressesLV.ItemsSource = DisplayedAddresses;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/LoginPage.xaml b/uwp/cs/07_AdvancedChat/Views/LoginPage.xaml
new file mode 100644
index 0000000..d2a543c
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/LoginPage.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/LoginPage.xaml.cs b/uwp/cs/07_AdvancedChat/Views/LoginPage.xaml.cs
new file mode 100644
index 0000000..fa03a08
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/LoginPage.xaml.cs
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _07_AdvancedChat.Service;
+using Linphone;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _07_AdvancedChat.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class LoginPage : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+
+ public LoginPage()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ CoreService.AddOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ }
+
+ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+ {
+ CoreService.RemoveOnAccountRegistrationStateChangedDelegate(OnAccountRegistrationStateChanged);
+ base.OnNavigatingFrom(e);
+ }
+
+ ///
+ /// Called when you click on the "Login" button.
+ ///
+ private void LogInClick(object sender, RoutedEventArgs e)
+ {
+ if (LogIn.IsEnabled)
+ {
+ LogIn.IsEnabled = false;
+
+ CoreService.LogIn(Identity.Text, Password.Password);
+ }
+ }
+
+ ///
+ /// Called when a key is pressed and released on the login page.
+ /// If you pressed "Enter", simulate a login click.
+ ///
+ private void GridKeyUp(object sender, KeyRoutedEventArgs e)
+ {
+ if (VirtualKey.Enter.Equals(e.Key))
+ {
+ LogInClick(null, null);
+ }
+ }
+
+ ///
+ /// This method is called every time the RegistrationState is updated by background core's actions.
+ /// In this example we use this to update the GUI.
+ ///
+ private void OnAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, string message)
+ {
+ RegistrationText.Text = "Your registration state is : " + state.ToString();
+ switch (state)
+ {
+ case RegistrationState.Cleared:
+ case RegistrationState.None:
+ CoreService.ClearCoreAfterLogOut();
+ LogIn.IsEnabled = true;
+ break;
+
+ case RegistrationState.Ok:
+ LogIn.IsEnabled = false;
+ this.Frame.Navigate(typeof(NavigationRoot));
+ break;
+
+ case RegistrationState.Progress:
+ LogIn.IsEnabled = false;
+ break;
+
+ case RegistrationState.Failed:
+ LogIn.IsEnabled = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/NavigationRoot.xaml b/uwp/cs/07_AdvancedChat/Views/NavigationRoot.xaml
new file mode 100644
index 0000000..749e4ba
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/NavigationRoot.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uwp/cs/07_AdvancedChat/Views/NavigationRoot.xaml.cs b/uwp/cs/07_AdvancedChat/Views/NavigationRoot.xaml.cs
new file mode 100644
index 0000000..d2e944c
--- /dev/null
+++ b/uwp/cs/07_AdvancedChat/Views/NavigationRoot.xaml.cs
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of Linphone TutorialCS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using _07_AdvancedChat.Service;
+using Linphone;
+using System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Navigation;
+
+namespace _07_AdvancedChat.Views
+{
+ ///
+ /// A really simple app for a first Login with LinphoneSDK x UWP
+ ///
+ public sealed partial class NavigationRoot : Page
+ {
+ private CoreService CoreService { get; } = CoreService.Instance;
+ private NavigationService NavigationService { get; } = NavigationService.Instance;
+ private bool hasLoadedPreviously;
+
+ public NavigationRoot()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+ this.CoreService.AddOnOnMessageReceivedDelegate(OnMessageReveive);
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ this.CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReveive);
+ base.OnNavigatedFrom(e);
+ }
+
+ private void Page_Loaded(object sender, RoutedEventArgs e)
+ {
+ // Only do an inital navigate the first time the page loads
+ // when we switch out of compactoverloadmode this will fire but we don't want to navigate because
+ // there is already a page loaded
+ if (!hasLoadedPreviously)
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ UpdateUnreadMessageCount();
+ hasLoadedPreviously = true;
+ NavigationService.CurrentNavigationRoot = this;
+ }
+ }
+
+ private void AppNavFrame_Navigated(object sender, NavigationEventArgs e)
+ {
+ switch (e.SourcePageType)
+ {
+ case Type c when e.SourcePageType == typeof(CallsPage):
+ ((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
+ break;
+
+ case Type c when e.SourcePageType == typeof(ChatsPage):
+ ((NavigationViewItem)navview.MenuItems[1]).IsSelected = true;
+ break;
+ }
+ }
+
+ private async void Navview_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
+ {
+ if (args.IsSettingsInvoked)
+ {
+ ContentDialog noSettingsDialog = new ContentDialog
+ {
+ Title = "No settings",
+ Content = "There is no settings in this little app",
+ CloseButtonText = "OK"
+ };
+
+ ContentDialogResult result = await noSettingsDialog.ShowAsync();
+ return;
+ }
+
+ string invokedItemValue = args.InvokedItem as string;
+ if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
+ {
+ AppNavFrame.Navigate(typeof(CallsPage));
+ }
+ else
+ {
+ AppNavFrame.Navigate(typeof(ChatsPage));
+ }
+ }
+
+ private void SignOut_Tapped(object sender, TappedRoutedEventArgs e)
+ {
+ DisplaySignOutDialog();
+ }
+
+ private async void DisplaySignOutDialog()
+ {
+ ContentDialog signOutDialog = new ContentDialog
+ {
+ Title = "Sign out ?",
+ Content = "All your current calls and actions will be canceled, are you sure to continue ?",
+ PrimaryButtonText = "Sign out",
+ CloseButtonText = "Cancel"
+ };
+
+ ContentDialogResult result = await signOutDialog.ShowAsync();
+
+ if (result == ContentDialogResult.Primary)
+ {
+ CoreService.Core.TerminateAllCalls();
+ CoreService.LogOut();
+
+ this.Frame.Navigate(typeof(LoginPage));
+ }
+ }
+
+ private void OnMessageReveive(Core core, ChatRoom chatRoom, ChatMessage message)
+ {
+ UpdateUnreadMessageCount();
+ }
+
+ public void UpdateUnreadMessageCount()
+ {
+ if (CoreService.Core.UnreadChatMessageCountFromActiveLocals > 0)
+ {
+ NewMessageCount.Text = "" + CoreService.Core.UnreadChatMessageCountFromActiveLocals;
+ NewMessageCountBorder.Visibility = Visibility.Visible;
+ }
+ else
+ {
+ NewMessageCountBorder.Visibility = Visibility.Collapsed;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uwp/cs/Readme.md b/uwp/cs/Readme.md
new file mode 100644
index 0000000..1dc0d7d
--- /dev/null
+++ b/uwp/cs/Readme.md
@@ -0,0 +1,22 @@
+Linphone X UWP tutorial
+=======================
+
+Welcome to the C# tutorial, we are going to learn how to use the LinphoneSDK in the UWP (Universal Windows Platform) environment.
+
+We recommend you to use Visual Studio 2019 to follow this tutorial.
+
+First of all you can open the TutorialsCS solution in Visual Studio (TutorialsCS.sln) .
+Under the solution you will find several projects, each is a step from a hello world to a nearly full communication app using LinphoneSDK.
+Each step work and can be run independently, and in each step the new "Linphone" code is explained.
+
+Don't forget to get a look at [Liblinphone doc](https://linphone.org/releases/docs/liblinphone/5.0/cs/) to see the complete list of available APIs.
+
+You can find a readme file in each project, explaining the project goal and how to run and understand it.
+ - [00_HelloWorld](00_HelloWorld/) : A simple "Hello World" with Linphone version number.
+ - [01_AccountLogin](01_AccountLogin/) : Learn how to login to your SIP account.
+ - [02_IncomingCall](02_IncomingCall/) : Receive calls.
+ - [03_OutgoingCall](03_OutgoingCall/) : Make your first calls.
+ - [04_BasicChat](04_BasicChat/) : Learn how to manage and send message in basic chat rooms.
+ - [05_FileTransfer](05_FileTransfer/) : Send files in your previously created chat rooms.
+ - [06_GroupChat](06_GroupChat/) : Create and manage group chat rooms.
+ - [07_AdvancedChat](07_AdvancedChat/) : Secure chat rooms, ephemeral messages.
\ No newline at end of file
diff --git a/uwp/cs/TutorialsCS.sln b/uwp/cs/TutorialsCS.sln
new file mode 100644
index 0000000..afab073
--- /dev/null
+++ b/uwp/cs/TutorialsCS.sln
@@ -0,0 +1,238 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30804.86
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "00_HelloWorld", "00_HelloWorld\00_HelloWorld.csproj", "{4EDDB112-127A-42C3-81BE-5BA16151E67F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01_AccountLogin", "01_AccountLogin\01_AccountLogin.csproj", "{E275B25F-D5C2-495C-9B6E-9B90C3617E98}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02_IncomingCall", "02_IncomingCall\02_IncomingCall.csproj", "{8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "04_BasicChat", "04_BasicChat\04_BasicChat.csproj", "{22034C73-FF8C-4D02-874D-15FED72C1A1D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "05_FileTransfer", "05_FileTransfer\05_FileTransfer.csproj", "{C3BB19EB-D50B-4FEB-A097-899C67281A13}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{248A2D49-EC1F-4957-B6C4-E88A0D854CE6}"
+ ProjectSection(SolutionItems) = preProject
+ Readme.md = Readme.md
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "06_GroupChat", "06_GroupChat\06_GroupChat.csproj", "{5D45D2C2-C228-4C2D-A376-3606FDB16686}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "07_AdvancedChat", "07_AdvancedChat\07_AdvancedChat.csproj", "{C0C99544-E7DE-4CF5-87B9-52677828EC24}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "03_OutgoingCall", "03_OutgoingCall\03_OutgoingCall.csproj", "{59E85455-ACC8-4F02-8E80-AD3C91167AF9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|ARM = Debug|ARM
+ Debug|ARM64 = Debug|ARM64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|ARM = Release|ARM
+ Release|ARM64 = Release|ARM64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|ARM.ActiveCfg = Debug|ARM
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|ARM.Build.0 = Debug|ARM
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|ARM.Deploy.0 = Debug|ARM
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|ARM64.Build.0 = Debug|ARM64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|x64.ActiveCfg = Debug|x64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|x64.Build.0 = Debug|x64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|x64.Deploy.0 = Debug|x64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|x86.ActiveCfg = Debug|x86
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|x86.Build.0 = Debug|x86
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Debug|x86.Deploy.0 = Debug|x86
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|ARM.ActiveCfg = Release|ARM
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|ARM.Build.0 = Release|ARM
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|ARM.Deploy.0 = Release|ARM
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|ARM64.ActiveCfg = Release|ARM64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|ARM64.Build.0 = Release|ARM64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|ARM64.Deploy.0 = Release|ARM64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|x64.ActiveCfg = Release|x64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|x64.Build.0 = Release|x64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|x64.Deploy.0 = Release|x64
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|x86.ActiveCfg = Release|x86
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|x86.Build.0 = Release|x86
+ {4EDDB112-127A-42C3-81BE-5BA16151E67F}.Release|x86.Deploy.0 = Release|x86
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|ARM.ActiveCfg = Debug|ARM
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|ARM.Build.0 = Debug|ARM
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|ARM.Deploy.0 = Debug|ARM
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|ARM64.Build.0 = Debug|ARM64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|x64.ActiveCfg = Debug|x64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|x64.Build.0 = Debug|x64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|x64.Deploy.0 = Debug|x64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|x86.ActiveCfg = Debug|x86
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|x86.Build.0 = Debug|x86
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Debug|x86.Deploy.0 = Debug|x86
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|ARM.ActiveCfg = Release|ARM
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|ARM.Build.0 = Release|ARM
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|ARM.Deploy.0 = Release|ARM
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|ARM64.ActiveCfg = Release|ARM64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|ARM64.Build.0 = Release|ARM64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|ARM64.Deploy.0 = Release|ARM64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|x64.ActiveCfg = Release|x64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|x64.Build.0 = Release|x64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|x64.Deploy.0 = Release|x64
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|x86.ActiveCfg = Release|x86
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|x86.Build.0 = Release|x86
+ {E275B25F-D5C2-495C-9B6E-9B90C3617E98}.Release|x86.Deploy.0 = Release|x86
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|ARM.ActiveCfg = Debug|ARM
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|ARM.Build.0 = Debug|ARM
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|ARM.Deploy.0 = Debug|ARM
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|ARM64.Build.0 = Debug|ARM64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|x64.ActiveCfg = Debug|x64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|x64.Build.0 = Debug|x64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|x64.Deploy.0 = Debug|x64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|x86.ActiveCfg = Debug|x86
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|x86.Build.0 = Debug|x86
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Debug|x86.Deploy.0 = Debug|x86
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|ARM.ActiveCfg = Release|ARM
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|ARM.Build.0 = Release|ARM
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|ARM.Deploy.0 = Release|ARM
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|ARM64.ActiveCfg = Release|ARM64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|ARM64.Build.0 = Release|ARM64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|ARM64.Deploy.0 = Release|ARM64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|x64.ActiveCfg = Release|x64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|x64.Build.0 = Release|x64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|x64.Deploy.0 = Release|x64
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|x86.ActiveCfg = Release|x86
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|x86.Build.0 = Release|x86
+ {8C3B09A6-CD10-4F83-8EF1-6BF2002C6339}.Release|x86.Deploy.0 = Release|x86
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|ARM.ActiveCfg = Debug|ARM
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|ARM.Build.0 = Debug|ARM
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|ARM.Deploy.0 = Debug|ARM
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|ARM64.Build.0 = Debug|ARM64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|x64.ActiveCfg = Debug|x64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|x64.Build.0 = Debug|x64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|x64.Deploy.0 = Debug|x64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|x86.ActiveCfg = Debug|x86
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|x86.Build.0 = Debug|x86
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Debug|x86.Deploy.0 = Debug|x86
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|ARM.ActiveCfg = Release|ARM
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|ARM.Build.0 = Release|ARM
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|ARM.Deploy.0 = Release|ARM
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|ARM64.ActiveCfg = Release|ARM64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|ARM64.Build.0 = Release|ARM64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|ARM64.Deploy.0 = Release|ARM64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|x64.ActiveCfg = Release|x64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|x64.Build.0 = Release|x64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|x64.Deploy.0 = Release|x64
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|x86.ActiveCfg = Release|x86
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|x86.Build.0 = Release|x86
+ {22034C73-FF8C-4D02-874D-15FED72C1A1D}.Release|x86.Deploy.0 = Release|x86
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|ARM.ActiveCfg = Debug|ARM
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|ARM.Build.0 = Debug|ARM
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|ARM.Deploy.0 = Debug|ARM
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|ARM64.Build.0 = Debug|ARM64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|x64.ActiveCfg = Debug|x64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|x64.Build.0 = Debug|x64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|x64.Deploy.0 = Debug|x64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|x86.ActiveCfg = Debug|x86
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|x86.Build.0 = Debug|x86
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Debug|x86.Deploy.0 = Debug|x86
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|ARM.ActiveCfg = Release|ARM
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|ARM.Build.0 = Release|ARM
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|ARM.Deploy.0 = Release|ARM
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|ARM64.ActiveCfg = Release|ARM64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|ARM64.Build.0 = Release|ARM64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|ARM64.Deploy.0 = Release|ARM64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|x64.ActiveCfg = Release|x64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|x64.Build.0 = Release|x64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|x64.Deploy.0 = Release|x64
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|x86.ActiveCfg = Release|x86
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|x86.Build.0 = Release|x86
+ {C3BB19EB-D50B-4FEB-A097-899C67281A13}.Release|x86.Deploy.0 = Release|x86
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|ARM.ActiveCfg = Debug|ARM
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|ARM.Build.0 = Debug|ARM
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|ARM.Deploy.0 = Debug|ARM
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|ARM64.Build.0 = Debug|ARM64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|x64.ActiveCfg = Debug|x64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|x64.Build.0 = Debug|x64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|x64.Deploy.0 = Debug|x64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|x86.ActiveCfg = Debug|x86
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|x86.Build.0 = Debug|x86
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Debug|x86.Deploy.0 = Debug|x86
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|ARM.ActiveCfg = Release|ARM
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|ARM.Build.0 = Release|ARM
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|ARM.Deploy.0 = Release|ARM
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|ARM64.ActiveCfg = Release|ARM64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|ARM64.Build.0 = Release|ARM64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|ARM64.Deploy.0 = Release|ARM64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|x64.ActiveCfg = Release|x64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|x64.Build.0 = Release|x64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|x64.Deploy.0 = Release|x64
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|x86.ActiveCfg = Release|x86
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|x86.Build.0 = Release|x86
+ {5D45D2C2-C228-4C2D-A376-3606FDB16686}.Release|x86.Deploy.0 = Release|x86
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|ARM.ActiveCfg = Debug|ARM
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|ARM.Build.0 = Debug|ARM
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|ARM.Deploy.0 = Debug|ARM
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|ARM64.Build.0 = Debug|ARM64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|x64.ActiveCfg = Debug|x64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|x64.Build.0 = Debug|x64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|x64.Deploy.0 = Debug|x64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|x86.ActiveCfg = Debug|x86
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|x86.Build.0 = Debug|x86
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Debug|x86.Deploy.0 = Debug|x86
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|ARM.ActiveCfg = Release|ARM
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|ARM.Build.0 = Release|ARM
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|ARM.Deploy.0 = Release|ARM
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|ARM64.ActiveCfg = Release|ARM64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|ARM64.Build.0 = Release|ARM64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|ARM64.Deploy.0 = Release|ARM64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|x64.ActiveCfg = Release|x64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|x64.Build.0 = Release|x64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|x64.Deploy.0 = Release|x64
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|x86.ActiveCfg = Release|x86
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|x86.Build.0 = Release|x86
+ {C0C99544-E7DE-4CF5-87B9-52677828EC24}.Release|x86.Deploy.0 = Release|x86
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|ARM.ActiveCfg = Debug|ARM
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|ARM.Build.0 = Debug|ARM
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|ARM.Deploy.0 = Debug|ARM
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|ARM64.Build.0 = Debug|ARM64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|x64.ActiveCfg = Debug|x64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|x64.Build.0 = Debug|x64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|x64.Deploy.0 = Debug|x64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|x86.ActiveCfg = Debug|x86
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|x86.Build.0 = Debug|x86
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Debug|x86.Deploy.0 = Debug|x86
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|ARM.ActiveCfg = Release|ARM
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|ARM.Build.0 = Release|ARM
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|ARM.Deploy.0 = Release|ARM
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|ARM64.ActiveCfg = Release|ARM64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|ARM64.Build.0 = Release|ARM64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|ARM64.Deploy.0 = Release|ARM64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|x64.ActiveCfg = Release|x64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|x64.Build.0 = Release|x64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|x64.Deploy.0 = Release|x64
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|x86.ActiveCfg = Release|x86
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|x86.Build.0 = Release|x86
+ {59E85455-ACC8-4F02-8E80-AD3C91167AF9}.Release|x86.Deploy.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C6C912AD-1E23-48B5-AE70-D12A292C5124}
+ EndGlobalSection
+EndGlobal