diff --git a/Microsoft.Toolkit.Graph.Controls/Assets/person.png b/Microsoft.Toolkit.Graph.Controls/Assets/person.png
new file mode 100644
index 0000000..3566862
Binary files /dev/null and b/Microsoft.Toolkit.Graph.Controls/Assets/person.png differ
diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/PersonView/PersonView.Properties.cs b/Microsoft.Toolkit.Graph.Controls/Controls/PersonView/PersonView.Properties.cs
new file mode 100644
index 0000000..84fdbed
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Controls/PersonView/PersonView.Properties.cs
@@ -0,0 +1,165 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Graph;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media.Imaging;
+
+namespace Microsoft.Toolkit.Graph.Controls
+{
+ ///
+ /// The control displays a user photo and can display their name and e-mail.
+ ///
+ public partial class PersonView
+ {
+ ///
+ /// Gets or sets details about this person retrieved from the graph or provided by the developer.
+ ///
+ public Person PersonDetails
+ {
+ get { return (Person)GetValue(PersonDetailsProperty); }
+ set { SetValue(PersonDetailsProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty PersonDetailsProperty =
+ DependencyProperty.Register(nameof(PersonDetails), typeof(Person), typeof(PersonView), new PropertyMetadata(null, PersonDetailsPropertyChanged));
+
+ ///
+ /// Gets or sets a string to automatically retrieve data on the specified query from the graph. Use to retrieve info about the current user. Otherwise, it's best to use an e-mail address as a query.
+ ///
+ public string PersonQuery
+ {
+ get { return (string)GetValue(PersonQueryProperty); }
+ set { SetValue(PersonQueryProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty PersonQueryProperty =
+ DependencyProperty.Register(nameof(PersonQuery), typeof(string), typeof(PersonView), new PropertyMetadata(null, QueryPropertyChanged));
+
+ ///
+ /// Gets or sets the UserId.
+ ///
+ public string UserId
+ {
+ get { return (string)GetValue(UserIdProperty); }
+ set { SetValue(UserIdProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty UserIdProperty =
+ DependencyProperty.Register(nameof(UserId), typeof(string), typeof(PersonView), new PropertyMetadata(null, QueryPropertyChanged));
+
+ ///
+ /// Gets or sets a value indicating whether the user's name should be displayed.
+ ///
+ public bool ShowName
+ {
+ get { return (bool)GetValue(ShowNameProperty); }
+ set { SetValue(ShowNameProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty ShowNameProperty =
+ DependencyProperty.Register(nameof(ShowName), typeof(bool), typeof(PersonView), new PropertyMetadata(false, ShowDisplayPropertiesChanged));
+
+ ///
+ /// Gets or sets a value indicating whether the user's email address should be displayed.
+ ///
+ public bool ShowEmail
+ {
+ get { return (bool)GetValue(ShowEmailProperty); }
+ set { SetValue(ShowEmailProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty ShowEmailProperty =
+ DependencyProperty.Register(nameof(ShowEmail), typeof(bool), typeof(PersonView), new PropertyMetadata(false, ShowDisplayPropertiesChanged));
+
+ ///
+ /// Gets or sets the photo of the user to be displayed.
+ ///
+ public BitmapImage UserPhoto
+ {
+ get { return (BitmapImage)GetValue(UserPhotoProperty); }
+ set { SetValue(UserPhotoProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty UserPhotoProperty =
+ DependencyProperty.Register(nameof(UserPhoto), typeof(BitmapImage), typeof(PersonView), new PropertyMetadata(null));
+
+ ///
+ /// Gets the initials of the person from the .
+ ///
+ public string Initials
+ {
+ get { return (string)GetValue(InitialsProperty); }
+ internal set { SetValue(InitialsProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty InitialsProperty =
+ DependencyProperty.Register(nameof(Initials), typeof(string), typeof(PersonView), new PropertyMetadata(string.Empty));
+
+ ///
+ /// Gets a value indicating whether the image has expanded because both and are enabled.
+ ///
+ public bool IsLargeImage
+ {
+ get { return (bool)GetValue(IsLargeImageProperty); }
+ internal set { SetValue(IsLargeImageProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty IsLargeImageProperty =
+ DependencyProperty.Register(nameof(IsLargeImage), typeof(bool), typeof(PersonView), new PropertyMetadata(false));
+ }
+}
diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/PersonView/PersonView.cs b/Microsoft.Toolkit.Graph.Controls/Controls/PersonView/PersonView.cs
new file mode 100644
index 0000000..ab71617
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Controls/PersonView/PersonView.cs
@@ -0,0 +1,222 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Graph;
+using Microsoft.Toolkit.Graph.Helpers;
+using Microsoft.Toolkit.Graph.Providers;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media.Imaging;
+
+namespace Microsoft.Toolkit.Graph.Controls
+{
+ ///
+ /// The control displays a user photo and can display their name and e-mail.
+ ///
+ public partial class PersonView : Control
+ {
+ ///
+ /// value used to retrieve the signed-in user's info.
+ ///
+ public const string PersonQueryMe = "me";
+
+ private static readonly string[] RequiredScopes = new string[] { "user.readbasic.all" };
+
+ private string _photoId = null;
+
+ private static async void PersonDetailsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is PersonView pv)
+ {
+ if (pv.PersonDetails != null)
+ {
+ if (pv.PersonDetails.GivenName?.Length > 0 && pv.PersonDetails.Surname?.Length > 0)
+ {
+ pv.Initials = string.Empty + pv.PersonDetails.GivenName[0] + pv.PersonDetails.Surname[0];
+ }
+ else if (pv.PersonDetails.DisplayName?.Length > 0)
+ {
+ // Grab first two initials in name
+ var initials = pv.PersonDetails.DisplayName.ToUpper().Split(' ').Select(i => i.First());
+ pv.Initials = string.Join(string.Empty, initials.Where(i => char.IsLetter(i)).Take(2));
+ }
+
+ if (pv.UserPhoto == null || pv.PersonDetails.Id != pv._photoId)
+ {
+ // Reload Image
+ pv.UserPhoto = null;
+ await pv.LoadImageAsync(pv.PersonDetails);
+ }
+ else if (pv.PersonDetails.Id != pv._photoId)
+ {
+ pv.UserPhoto = null;
+ pv._photoId = null;
+ }
+ }
+ }
+ }
+
+ private static void QueryPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is PersonView view)
+ {
+ view.PersonDetails = null;
+ view.LoadData();
+ }
+ }
+
+ private static void ShowDisplayPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is PersonView pv)
+ {
+ pv.IsLargeImage = pv.ShowName && pv.ShowEmail;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PersonView()
+ {
+ this.DefaultStyleKey = typeof(PersonView);
+
+ ProviderManager.Instance.ProviderUpdated += (sender, args) => LoadData();
+ }
+
+ ///
+ protected override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ LoadData();
+ }
+
+ private async void LoadData()
+ {
+ var provider = ProviderManager.Instance.GlobalProvider;
+
+ if (provider == null || provider.State != ProviderState.SignedIn)
+ {
+ return;
+ }
+
+ if (PersonDetails != null && UserPhoto == null)
+ {
+ await LoadImageAsync(PersonDetails);
+ }
+ else if (!string.IsNullOrWhiteSpace(UserId) || PersonQuery?.ToLowerInvariant() == PersonQueryMe)
+ {
+ User user = null;
+ if (!string.IsNullOrWhiteSpace(UserId))
+ {
+ // TODO: Batch when API easier https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/issues/29
+ try
+ {
+ user = await provider.Graph.Users[UserId].Request().GetAsync();
+ }
+ catch
+ {
+ }
+
+ try
+ {
+ // TODO: Move to LoadImage based on previous call?
+ await DecodeStreamAsync(await provider.Graph.Users[UserId].Photo.Content.Request().GetAsync());
+ _photoId = UserId;
+ }
+ catch
+ {
+ }
+ }
+ else
+ {
+ try
+ {
+ user = await provider.Graph.Me.Request().GetAsync();
+ }
+ catch
+ {
+ }
+
+ try
+ {
+ await DecodeStreamAsync(await provider.Graph.Me.Photo.Content.Request().GetAsync());
+ _photoId = user.Id;
+ }
+ catch
+ {
+ }
+ }
+
+ if (user != null)
+ {
+ PersonDetails = new Person()
+ {
+ Id = user.Id,
+ DisplayName = user.DisplayName,
+ ScoredEmailAddresses = new ScoredEmailAddress[]
+ {
+ new ScoredEmailAddress()
+ {
+ Address = user.Mail ?? user.UserPrincipalName
+ }
+ },
+ GivenName = user.GivenName,
+ Surname = user.Surname
+ };
+ }
+ }
+ else if (PersonDetails == null && !string.IsNullOrWhiteSpace(PersonQuery))
+ {
+ var people = await provider.Graph.FindPersonAsync(PersonQuery);
+ if (people != null && people.Count > 0)
+ {
+ var person = people.FirstOrDefault();
+ PersonDetails = person;
+ await LoadImageAsync(person);
+ }
+ }
+ }
+
+ private async Task LoadImageAsync(Person person)
+ {
+ try
+ {
+ // TODO: Better guarding
+ var graph = ProviderManager.Instance.GlobalProvider.Graph;
+
+ if (!string.IsNullOrWhiteSpace(person.UserPrincipalName))
+ {
+ await DecodeStreamAsync(await graph.GetUserPhoto(person.UserPrincipalName));
+ _photoId = person.Id; // TODO: Only set on success for photo?
+ }
+ else if (!string.IsNullOrWhiteSpace(person.ScoredEmailAddresses.First().Address))
+ {
+ // TODO https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/master/src/components/mgt-person/mgt-person.ts#L174
+ }
+ }
+ catch
+ {
+ // If we can't load a photo, that's ok.
+ }
+ }
+
+ private async Task DecodeStreamAsync(Stream photoStream)
+ {
+ if (photoStream != null)
+ {
+ using (var ras = photoStream.AsRandomAccessStream())
+ {
+ var bitmap = new BitmapImage();
+ await bitmap.SetSourceAsync(ras);
+ UserPhoto = bitmap;
+ }
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/PersonView/PersonView.xaml b/Microsoft.Toolkit.Graph.Controls/Controls/PersonView/PersonView.xaml
new file mode 100644
index 0000000..9957ed5
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Controls/PersonView/PersonView.xaml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj b/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj
new file mode 100644
index 0000000..74a0b31
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj
@@ -0,0 +1,53 @@
+
+
+
+ uap10.0.16299
+ Windows Community Toolkit Graph Controls
+ Microsoft.Toolkit.Graph.Controls
+
+ This library provides Microsoft Graph XAML controls. It is part of the Windows Community Toolkit.
+
+ Classes:
+ - LoginButton: The Login Control leverages MSAL libraries to support the sign-in processes for Microsoft Graph and beyond.
+ - PersonView: The PersonView control displays a user photo and can display their name and e-mail.
+ - PeoplePicker: The PeoplePicker Control is a simple control that allows for selection of one or more users.
+
+ UWP Toolkit Windows Controls MSAL Microsoft Graph AadLogin ProfileCard Person PeoplePicker Login
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Graph.Controls/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.Graph.Controls/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..c244792
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Resources;
+using System.Runtime.CompilerServices;
+
+// 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: NeutralResourcesLanguage("en-US")]
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Graph.Controls/Properties/Microsoft.Toolkit.Graph.Controls.rd.xml b/Microsoft.Toolkit.Graph.Controls/Properties/Microsoft.Toolkit.Graph.Controls.rd.xml
new file mode 100644
index 0000000..24ec172
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Properties/Microsoft.Toolkit.Graph.Controls.rd.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderWrapper.Properties.cs b/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderWrapper.Properties.cs
new file mode 100644
index 0000000..5f6dc26
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderWrapper.Properties.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.UI.Xaml;
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// Properties for .
+ ///
+ public partial class CommonProviderWrapper
+ {
+ ///
+ /// Gets or sets the Client ID (the unique application (client) ID assigned to your app by Azure AD when the app was registered).
+ ///
+ ///
+ /// For details about how to register an app and get a client ID,
+ /// see the Register an app quick start.
+ ///
+ public string ClientId
+ {
+ get { return (string)GetValue(ClientIdProperty); }
+ set { SetValue(ClientIdProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty ClientIdProperty =
+ DependencyProperty.Register(nameof(ClientId), typeof(string), typeof(InteractiveProvider), new PropertyMetadata(string.Empty, ClientIdPropertyChanged));
+
+ ///
+ /// Gets or sets the redirect URI (the URI the identity provider will send the security tokens back to).
+ ///
+ public string RedirectUri
+ {
+ get { return (string)GetValue(RedirectUriProperty); }
+ set { SetValue(RedirectUriProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for the dependency property.
+ ///
+ public static readonly DependencyProperty RedirectUriProperty =
+ DependencyProperty.Register(nameof(RedirectUri), typeof(string), typeof(CommonProviderWrapper), new PropertyMetadata("https://login.microsoftonline.com/common/oauth2/nativeclient"));
+
+ ///
+ /// Gets or sets the list of Scopes (permissions) to request on initial login.
+ ///
+ ///
+ /// This list can be modified by controls which require specific scopes to function. This will aid in requesting all scopes required by controls used before login is initiated, if using the LoginButton.
+ ///
+ public ScopeSet Scopes { get; set; } = new ScopeSet { "User.ReadBasic.All" };
+ }
+}
diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderWrapper.cs b/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderWrapper.cs
new file mode 100644
index 0000000..febda65
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderWrapper.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.Graph;
+using Windows.UI.Xaml;
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// Provides a common base class for UWP XAML based provider wrappers to the Microsoft.Graph.Auth SDK.
+ ///
+ public abstract partial class CommonProviderWrapper : DependencyObject
+ {
+ private static async void ClientIdPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is CommonProviderWrapper provider)
+ {
+ await provider.InitializeAsync();
+ }
+ }
+
+ ///
+ /// Called by developers to easily initialize MSAL and a Provider when calling from WPF via XAML Islands.
+ ///
+ /// CommonProvider type to initialize
+ /// Optional shortcut to initialize the ClientId parameter
+ /// Optional shortcut to initialize the RedirectUri parameter
+ /// New instance.
+ public static async Task InitializeAsync(string clientId = null, string redirectUri = null)
+ where T : CommonProviderWrapper, new()
+ {
+ var provider = new T
+ {
+ ClientId = clientId,
+ RedirectUri = redirectUri
+ };
+
+ await provider.InitializeAsync();
+
+ return provider;
+ }
+
+ ///
+ /// Used by controls to request the additional scopes they require.
+ ///
+ /// Scopes to request
+ public void RequestAdditionalScopes(params string[] scopes)
+ {
+ Scopes.AddRange(scopes);
+ }
+
+ ///
+ /// Called when provider is initialized from XAML (UWP Only).
+ ///
+ /// A representing the asynchronous operation.
+ protected abstract Task InitializeAsync();
+ }
+}
diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/InteractiveProvider.cs b/Microsoft.Toolkit.Graph.Controls/Providers/InteractiveProvider.cs
new file mode 100644
index 0000000..441c5eb
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Providers/InteractiveProvider.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.Graph.Auth;
+using Microsoft.Identity.Client;
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// Put in app.xaml resources with ClientId
+ /// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Acquiring-tokens-interactively
+ ///
+ ///
+ ///
+ /// <wgt:InteractiveProvider x:Key="MyProvider" ClientId="MyClientIdGuid"/%gt;
+ ///
+ ///
+ public class InteractiveProvider : CommonProviderWrapper
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public InteractiveProvider()
+ {
+ }
+
+ ///
+ protected override async Task InitializeAsync()
+ {
+ var client = PublicClientApplicationBuilder.Create(ClientId)
+ .WithAuthority(AzureCloudInstance.AzurePublic, AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount)
+ .WithRedirectUri(RedirectUri)
+ .WithClientName(ProviderManager.ClientName)
+ .WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString())
+ .Build();
+
+ var provider = new InteractiveAuthenticationProvider(client, Scopes);
+
+ ProviderManager.Instance.GlobalProvider = await MsalProvider.CreateAsync(client, provider);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/ScopeSet.cs b/Microsoft.Toolkit.Graph.Controls/Providers/ScopeSet.cs
new file mode 100644
index 0000000..ad1114e
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Providers/ScopeSet.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// Helper Class for XAML string Scope conversion.
+ ///
+ [Windows.Foundation.Metadata.CreateFromString(MethodName = "Microsoft.Toolkit.Graph.Providers.ScopeSet.ConvertToScopeArray")]
+ public class ScopeSet : List
+ {
+ ///
+ /// Helper to convert a string of scopes to a list of strings.
+ ///
+ /// Comma separated scope list.
+ /// New List of strings, i.e. ScopeSet
+ public static ScopeSet ConvertToScopeArray(string rawString)
+ {
+ if (rawString != null)
+ {
+ return (ScopeSet)rawString.Split(",").ToList();
+ }
+
+ return (ScopeSet)new List();
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Graph.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Graph.Controls/Themes/Generic.xaml
new file mode 100644
index 0000000..8d10be9
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/Themes/Generic.xaml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Graph.Controls/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Graph.Controls/VisualStudioToolsManifest.xml
new file mode 100644
index 0000000..79ff086
--- /dev/null
+++ b/Microsoft.Toolkit.Graph.Controls/VisualStudioToolsManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Graph/Helpers/GraphExtensions.cs b/Microsoft.Toolkit.Graph/Extensions/GraphExtensions.cs
similarity index 100%
rename from Microsoft.Toolkit.Graph/Helpers/GraphExtensions.cs
rename to Microsoft.Toolkit.Graph/Extensions/GraphExtensions.cs
diff --git a/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj b/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj
index 852bbb0..c7d1d16 100644
--- a/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj
+++ b/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj
@@ -1,9 +1,8 @@
- netstandard2.0
- Windows Community Toolkit .NET Standard Graph Helpers
- Microsoft.Toolkit.Graph
+ uap10.0.16299;netstandard2.0
+ Windows Community Toolkit .NET Standard Services
This package includes .NET Standard code helpers such as:
- GraphExtensions: Helpers for common tasks related to the Microsoft Graph used by the Microsoft.Toolkit.Graph.Controls.
@@ -14,7 +13,11 @@
Full
-
+
+
+ $(DefineConstants);WINRT
+
+
diff --git a/Microsoft.Toolkit.Graph/Providers/GlobalProvider.cs b/Microsoft.Toolkit.Graph/Providers/GlobalProvider.cs
deleted file mode 100644
index f7e7e1a..0000000
--- a/Microsoft.Toolkit.Graph/Providers/GlobalProvider.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.Graph;
-using Microsoft.Identity.Client;
-using Microsoft.Toolkit.Helpers;
-
-namespace Microsoft.Toolkit.Graph
-{
- ///
- /// Shared Provider used by controls in Microsoft.Toolkit.Graph.Controls to authenticate and call the Microsoft Graph.
- ///
- /// To set your own existing provider:
- ///
- /// TODO
- ///
- ///
- public class GlobalProvider
- {
- ///
- /// Gets the name of the toolkit client to identify self in Graph calls.
- ///
- public static readonly string ClientName = "Windows Community Toolkit" + ThisAssembly.AssemblyVersion;
-
- ///
- /// Gets the instance of the GlobalProvider
- ///
- public static GlobalProvider Instance => Singleton.Instance;
-
- ///
- /// Gets the to access the MicrosoftGraph.
- ///
- public GraphServiceClient Graph { get; private set; }
-
- private IAuthenticationProvider _provider;
-
- ///
- /// Gets or sets the Provider used for calls to the Microsoft.Graph SDK. Automatically constructs a new with that provider.
- ///
- public IAuthenticationProvider Provider
- {
- get
- {
- return _provider;
- }
-
- set
- {
- _provider = value;
- if (Graph == null && _provider != null)
- {
- Graph = new GraphServiceClient(_provider);
- }
- }
- }
-
- ///
- /// Gets or sets the reference used for MSAL.
- ///
- public IPublicClientApplication Client { get; set; }
-
- ///
- /// Logs out all users.
- ///
- public async void Logout()
- {
- foreach (var user in await Client.GetAccountsAsync())
- {
- await Client.RemoveAsync(user);
- }
- }
- }
-}
diff --git a/Microsoft.Toolkit.Graph/Providers/IProvider.cs b/Microsoft.Toolkit.Graph/Providers/IProvider.cs
new file mode 100644
index 0000000..4e089ea
--- /dev/null
+++ b/Microsoft.Toolkit.Graph/Providers/IProvider.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.ComponentModel;
+using System.Net.Http;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Microsoft.Graph;
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// helper wrapper to expose more states around the authentication process for graph controls.
+ ///
+ public interface IProvider : IAuthenticationProvider
+ {
+ ///
+ /// Gets the current login state of the provider.
+ ///
+ ProviderState State { get; }
+
+ ///
+ /// Gets the object to access the Microsoft Graph APIs.
+ ///
+ GraphServiceClient Graph { get; }
+
+ ///
+ /// Event called when the login changes.
+ ///
+ event EventHandler StateChanged;
+
+ ///
+ /// Login the user.
+ ///
+ ///
+ Task LoginAsync();
+
+ ///
+ /// Logout the user.
+ ///
+ ///
+ Task LogoutAsync();
+ }
+}
diff --git a/Microsoft.Toolkit.Graph/Providers/MsalProvider.cs b/Microsoft.Toolkit.Graph/Providers/MsalProvider.cs
new file mode 100644
index 0000000..66443e3
--- /dev/null
+++ b/Microsoft.Toolkit.Graph/Providers/MsalProvider.cs
@@ -0,0 +1,175 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.Graph;
+using Microsoft.Identity.Client;
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ //// TODO: Move some of this to a simple base-class for non-MSAL parts related to Provider only and properties?
+
+ ///
+ /// MSAL.NET provider helper for tracking authentication state using an class.
+ ///
+ public class MsalProvider : IProvider
+ {
+ ///
+ /// Gets or sets the MSAL.NET Client used to authenticate the user.
+ ///
+ protected IPublicClientApplication Client { get; set; }
+
+ ///
+ /// Gets or sets the provider used by the graph to manage requests.
+ ///
+ protected IAuthenticationProvider Provider { get; set; }
+
+ private ProviderState _state = ProviderState.Loading;
+
+ ///
+ public ProviderState State
+ {
+ get
+ {
+ return _state;
+ }
+
+ set
+ {
+ var current = _state;
+ _state = value;
+
+ StateChanged?.Invoke(this, new StateChangedEventArgs(current, _state));
+ }
+ }
+
+ ///
+ public GraphServiceClient Graph { get; private set; }
+
+ ///
+ public event EventHandler StateChanged;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ private MsalProvider()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Existing instance.
+ /// Existing instance.
+ /// A returning a instance.
+ public static async Task CreateAsync(IPublicClientApplication client, IAuthenticationProvider provider)
+ {
+ //// TODO: Check all config provided
+
+ var msal = new MsalProvider
+ {
+ Client = client,
+ Provider = provider,
+ Graph = new GraphServiceClient(provider)
+ };
+
+ await msal.TrySilentSignInAsync();
+
+ return msal;
+ }
+
+ ///
+ public async Task AuthenticateRequestAsync(HttpRequestMessage request)
+ {
+ try
+ {
+ await Provider.AuthenticateRequestAsync(request);
+ }
+ catch (Exception)
+ {
+ // TODO: Catch different types of errors and try and re-auth? Should be handled by Graph Auth Providers.
+ // Assume we're signed-out on error?
+ State = ProviderState.SignedOut;
+
+ return;
+ }
+
+ // Check state after request to see if we're now signed-in.
+ if (State != ProviderState.SignedIn)
+ {
+ if ((await Client.GetAccountsAsync()).Any())
+ {
+ State = ProviderState.SignedIn;
+ }
+ else
+ {
+ State = ProviderState.SignedOut;
+ }
+ }
+ }
+
+ ///
+ /// Tries to check if the user is logged in without prompting to login.
+ ///
+ /// A representing the asynchronous operation.
+ public async Task TrySilentSignInAsync()
+ {
+ var account = (await Client.GetAccountsAsync()).FirstOrDefault();
+
+ if (account == null)
+ {
+ // No accounts
+ State = ProviderState.SignedOut;
+ }
+ else
+ {
+ try
+ {
+ // Try and sign-in // TODO: can we use empty scopes?
+ var result = await Client.AcquireTokenSilent(new string[] { string.Empty }, account).ExecuteAsync();
+
+ if (!string.IsNullOrWhiteSpace(result.AccessToken))
+ {
+ State = ProviderState.SignedIn;
+ }
+ else
+ {
+ State = ProviderState.SignedOut;
+ }
+ }
+ catch (MsalUiRequiredException)
+ {
+ await LoginAsync();
+ }
+ catch (Exception)
+ {
+ State = ProviderState.SignedOut;
+ }
+ }
+ }
+
+ ///
+ public async Task LoginAsync()
+ {
+ // Force fake request to start auth process
+ await AuthenticateRequestAsync(new System.Net.Http.HttpRequestMessage());
+ }
+
+ ///
+ public async Task LogoutAsync()
+ {
+ // Forcibly remove each user.
+ foreach (var user in await Client.GetAccountsAsync())
+ {
+ await Client.RemoveAsync(user);
+ }
+
+ State = ProviderState.SignedOut;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs b/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs
new file mode 100644
index 0000000..39b29f2
--- /dev/null
+++ b/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs
@@ -0,0 +1,72 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.Graph;
+using Microsoft.Identity.Client;
+using Microsoft.Toolkit.Helpers;
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// Shared provider manager used by controls in Microsoft.Toolkit.Graph.Controls to authenticate and call the Microsoft Graph.
+ ///
+ /// To set your own existing provider:
+ ///
+ /// ProviderManager.Instance.GlobalProvider = await MsalProvider.CreateAsync(...);
+ ///
+ ///
+ public class ProviderManager
+ {
+ ///
+ /// Gets the name of the toolkit client to identify self in Graph calls.
+ ///
+ public static readonly string ClientName = "Windows Community Toolkit" + ThisAssembly.AssemblyVersion;
+
+ ///
+ /// Gets the instance of the GlobalProvider
+ ///
+ public static ProviderManager Instance => Singleton.Instance;
+
+ ///
+ /// Event called when the changes.
+ ///
+ public event EventHandler ProviderUpdated;
+
+ private IProvider _provider;
+
+ ///
+ /// Gets or sets the global provider used by all Microsoft.Toolkit.Graph.Controls.
+ ///
+ public IProvider GlobalProvider
+ {
+ get
+ {
+ return _provider;
+ }
+
+ set
+ {
+ if (_provider != null)
+ {
+ _provider.StateChanged -= ProviderStateChanged;
+ }
+
+ _provider = value;
+
+ if (_provider != null)
+ {
+ _provider.StateChanged += ProviderStateChanged;
+ }
+
+ ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderChanged));
+ }
+ }
+
+ private void ProviderStateChanged(object sender, StateChangedEventArgs e)
+ {
+ ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderStateChanged));
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Graph/Providers/ProviderManagerChangedState.cs b/Microsoft.Toolkit.Graph/Providers/ProviderManagerChangedState.cs
new file mode 100644
index 0000000..31ff1b9
--- /dev/null
+++ b/Microsoft.Toolkit.Graph/Providers/ProviderManagerChangedState.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// Enum representing reasons for provider state changing.
+ ///
+ public enum ProviderManagerChangedState
+ {
+ ///
+ /// The itself changed.
+ ///
+ ProviderChanged,
+
+ ///
+ /// The changed.
+ ///
+ ProviderStateChanged
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Graph/Providers/ProviderState.cs b/Microsoft.Toolkit.Graph/Providers/ProviderState.cs
new file mode 100644
index 0000000..ec7c856
--- /dev/null
+++ b/Microsoft.Toolkit.Graph/Providers/ProviderState.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// represents the current authentication state of the session for a given .
+ ///
+ public enum ProviderState
+ {
+ ///
+ /// The user's status is not known.
+ ///
+ Loading,
+
+ ///
+ /// The user is signed-out.
+ ///
+ SignedOut,
+
+ ///
+ /// The user is signed-in.
+ ///
+ SignedIn
+ }
+}
diff --git a/Microsoft.Toolkit.Graph/Providers/ProviderUpdatedEventArgs.cs b/Microsoft.Toolkit.Graph/Providers/ProviderUpdatedEventArgs.cs
new file mode 100644
index 0000000..4b652cb
--- /dev/null
+++ b/Microsoft.Toolkit.Graph/Providers/ProviderUpdatedEventArgs.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// class for event.
+ ///
+ public class ProviderUpdatedEventArgs : EventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// value for reason for update.
+ public ProviderUpdatedEventArgs(ProviderManagerChangedState reason)
+ {
+ Reason = reason;
+ }
+
+ ///
+ /// Gets the reason for the provider update.
+ ///
+ public ProviderManagerChangedState Reason { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Graph/Providers/StateChangedEventArgs.cs b/Microsoft.Toolkit.Graph/Providers/StateChangedEventArgs.cs
new file mode 100644
index 0000000..d795176
--- /dev/null
+++ b/Microsoft.Toolkit.Graph/Providers/StateChangedEventArgs.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Toolkit.Graph.Providers
+{
+ ///
+ /// event arguments.
+ ///
+ public class StateChangedEventArgs : EventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Previous
+ /// Current
+ public StateChangedEventArgs(ProviderState oldState, ProviderState newState)
+ {
+ OldState = oldState;
+ NewState = newState;
+ }
+
+ ///
+ /// Gets the previous state of the .
+ ///
+ public ProviderState OldState { get; private set; }
+
+ ///
+ /// Gets the new state of the .
+ ///
+ public ProviderState NewState { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/SampleTest/App.xaml b/SampleTest/App.xaml
new file mode 100644
index 0000000..2455161
--- /dev/null
+++ b/SampleTest/App.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/SampleTest/App.xaml.cs b/SampleTest/App.xaml.cs
new file mode 100644
index 0000000..cf46cb5
--- /dev/null
+++ b/SampleTest/App.xaml.cs
@@ -0,0 +1,104 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+namespace SampleTest
+{
+ ///
+ /// 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
+ 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();
+ }
+ }
+}
diff --git a/SampleTest/Assets/LockScreenLogo.scale-200.png b/SampleTest/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/SampleTest/Assets/LockScreenLogo.scale-200.png differ
diff --git a/SampleTest/Assets/SplashScreen.scale-200.png b/SampleTest/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/SampleTest/Assets/SplashScreen.scale-200.png differ
diff --git a/SampleTest/Assets/Square150x150Logo.scale-200.png b/SampleTest/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/SampleTest/Assets/Square150x150Logo.scale-200.png differ
diff --git a/SampleTest/Assets/Square44x44Logo.scale-200.png b/SampleTest/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/SampleTest/Assets/Square44x44Logo.scale-200.png differ
diff --git a/SampleTest/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/SampleTest/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/SampleTest/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/SampleTest/Assets/StoreLogo.png b/SampleTest/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/SampleTest/Assets/StoreLogo.png differ
diff --git a/SampleTest/Assets/Wide310x150Logo.scale-200.png b/SampleTest/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/SampleTest/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/SampleTest/MainPage.xaml b/SampleTest/MainPage.xaml
new file mode 100644
index 0000000..abe43db
--- /dev/null
+++ b/SampleTest/MainPage.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/SampleTest/MainPage.xaml.cs b/SampleTest/MainPage.xaml.cs
new file mode 100644
index 0000000..812a85b
--- /dev/null
+++ b/SampleTest/MainPage.xaml.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Toolkit.Graph.Providers;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
+
+namespace SampleTest
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainPage : Page
+ {
+ public MainPage()
+ {
+ this.InitializeComponent();
+ }
+
+ private async void Button_Click(object sender, RoutedEventArgs e)
+ {
+ if (ProviderManager.Instance.GlobalProvider.State != ProviderState.SignedIn)
+ {
+ await ProviderManager.Instance.GlobalProvider.LoginAsync();
+ }
+ else
+ {
+ await ProviderManager.Instance.GlobalProvider.LogoutAsync();
+ }
+ }
+ }
+}
diff --git a/SampleTest/Package.appxmanifest b/SampleTest/Package.appxmanifest
new file mode 100644
index 0000000..3be07e1
--- /dev/null
+++ b/SampleTest/Package.appxmanifest
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+ SampleTest
+ mhawker
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SampleTest/Properties/AssemblyInfo.cs b/SampleTest/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..67a172e
--- /dev/null
+++ b/SampleTest/Properties/AssemblyInfo.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+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("SampleTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SampleTest")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[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: ComVisible(false)]
\ No newline at end of file
diff --git a/SampleTest/Properties/Default.rd.xml b/SampleTest/Properties/Default.rd.xml
new file mode 100644
index 0000000..af00722
--- /dev/null
+++ b/SampleTest/Properties/Default.rd.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SampleTest/SampleTest.csproj b/SampleTest/SampleTest.csproj
new file mode 100644
index 0000000..c68c03d
--- /dev/null
+++ b/SampleTest/SampleTest.csproj
@@ -0,0 +1,178 @@
+
+
+
+
+ Debug
+ x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}
+ AppContainerExe
+ Properties
+ SampleTest
+ SampleTest
+ en-US
+ UAP
+ 10.0.18362.0
+ 10.0.16299.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
+
+
+
+
+ 6.2.9
+
+
+
+
+ {42252ee8-7e68-428f-972b-6d2dd3aa12cc}
+ Microsoft.Toolkit.Graph.Controls
+
+
+ {B2246169-0CD8-473C-AFF6-172310E2C3F6}
+ Microsoft.Toolkit.Graph
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/Windows-Toolkit-Graph-Controls.sln b/Windows-Toolkit-Graph-Controls.sln
index e722ee4..bea7918 100644
--- a/Windows-Toolkit-Graph-Controls.sln
+++ b/Windows-Toolkit-Graph-Controls.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.29230.61
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Graph", "Microsoft.Toolkit.Graph\Microsoft.Toolkit.Graph.csproj", "{B2246169-0CD8-473C-AFF6-172310E2C3F6}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Graph.Controls", "Microsoft.Toolkit.Graph.Controls\Microsoft.Toolkit.Graph.Controls.csproj", "{42252EE8-7E68-428F-972B-6D2DD3AA12CC}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build Config", "Build Config", "{B7903632-1BFF-4BA6-BD7A-9F8A897F7542}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
@@ -22,18 +24,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build Config", "Build Confi
version.json = version.json
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTest", "SampleTest\SampleTest.csproj", "{26F5807A-25B5-4E09-8C72-1749C4C59591}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM
+ Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Native|Any CPU = Native|Any CPU
Native|ARM = Native|ARM
+ Native|ARM64 = Native|ARM64
Native|x64 = Native|x64
Native|x86 = Native|x86
Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM
+ Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
@@ -42,6 +49,8 @@ Global
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|ARM.ActiveCfg = Debug|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|ARM.Build.0 = Debug|Any CPU
+ {B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|ARM64.Build.0 = Debug|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|x64.ActiveCfg = Debug|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|x64.Build.0 = Debug|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -50,6 +59,8 @@ Global
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Native|Any CPU.Build.0 = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Native|ARM.ActiveCfg = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Native|ARM.Build.0 = Release|Any CPU
+ {B2246169-0CD8-473C-AFF6-172310E2C3F6}.Native|ARM64.ActiveCfg = Release|Any CPU
+ {B2246169-0CD8-473C-AFF6-172310E2C3F6}.Native|ARM64.Build.0 = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Native|x64.ActiveCfg = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Native|x64.Build.0 = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Native|x86.ActiveCfg = Release|Any CPU
@@ -58,10 +69,75 @@ Global
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Release|Any CPU.Build.0 = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Release|ARM.ActiveCfg = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Release|ARM.Build.0 = Release|Any CPU
+ {B2246169-0CD8-473C-AFF6-172310E2C3F6}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {B2246169-0CD8-473C-AFF6-172310E2C3F6}.Release|ARM64.Build.0 = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Release|x64.ActiveCfg = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Release|x64.Build.0 = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Release|x86.ActiveCfg = Release|Any CPU
{B2246169-0CD8-473C-AFF6-172310E2C3F6}.Release|x86.Build.0 = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|ARM.Build.0 = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|x64.Build.0 = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|x86.Build.0 = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|Any CPU.ActiveCfg = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|Any CPU.Build.0 = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|ARM.ActiveCfg = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|ARM.Build.0 = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|ARM64.ActiveCfg = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|ARM64.Build.0 = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|x64.ActiveCfg = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|x64.Build.0 = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|x86.ActiveCfg = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Native|x86.Build.0 = Debug|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|ARM.ActiveCfg = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|ARM.Build.0 = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|ARM64.Build.0 = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|x64.ActiveCfg = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|x64.Build.0 = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|x86.ActiveCfg = Release|Any CPU
+ {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Release|x86.Build.0 = Release|Any CPU
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|Any CPU.Build.0 = Debug|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|Any CPU.Deploy.0 = Debug|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|ARM.ActiveCfg = Debug|ARM
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|ARM.Build.0 = Debug|ARM
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|ARM.Deploy.0 = Debug|ARM
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|ARM64.Build.0 = Debug|ARM64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|x64.ActiveCfg = Debug|x64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|x64.Build.0 = Debug|x64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|x64.Deploy.0 = Debug|x64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|x86.ActiveCfg = Debug|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|x86.Build.0 = Debug|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Debug|x86.Deploy.0 = Debug|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Native|Any CPU.ActiveCfg = Release|x64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Native|ARM.ActiveCfg = Release|ARM
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Native|ARM64.ActiveCfg = Release|ARM64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Native|x64.ActiveCfg = Release|x64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Native|x86.ActiveCfg = Release|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|Any CPU.ActiveCfg = Release|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|ARM.ActiveCfg = Release|ARM
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|ARM.Build.0 = Release|ARM
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|ARM.Deploy.0 = Release|ARM
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|ARM64.ActiveCfg = Release|ARM64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|ARM64.Build.0 = Release|ARM64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|ARM64.Deploy.0 = Release|ARM64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|x64.ActiveCfg = Release|x64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|x64.Build.0 = Release|x64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|x64.Deploy.0 = Release|x64
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|x86.ActiveCfg = Release|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|x86.Build.0 = Release|x86
+ {26F5807A-25B5-4E09-8C72-1749C4C59591}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE