diff --git a/Microsoft.Toolkit.Services/AssemblyInfo.cs b/Microsoft.Toolkit.Services/AssemblyInfo.cs
index 90ce2f74961..02efb81e9d2 100644
--- a/Microsoft.Toolkit.Services/AssemblyInfo.cs
+++ b/Microsoft.Toolkit.Services/AssemblyInfo.cs
@@ -11,4 +11,5 @@
// ******************************************************************
using System.Runtime.CompilerServices;
-[assembly:InternalsVisibleTo("Microsoft.Toolkit.Uwp.Services")]
\ No newline at end of file
+[assembly:InternalsVisibleTo("Microsoft.Toolkit.Uwp.Services")]
+[assembly:InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Controls.Graph")]
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Services/Services/MicrosoftGraph/MicrosoftGraphService.cs b/Microsoft.Toolkit.Services/Services/MicrosoftGraph/MicrosoftGraphService.cs
index f2f1551b1e4..8918a09bf73 100644
--- a/Microsoft.Toolkit.Services/Services/MicrosoftGraph/MicrosoftGraphService.cs
+++ b/Microsoft.Toolkit.Services/Services/MicrosoftGraph/MicrosoftGraphService.cs
@@ -12,6 +12,7 @@
using System;
using System.Net.Http.Headers;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph;
using Microsoft.Identity.Client;
@@ -24,11 +25,18 @@ namespace Microsoft.Toolkit.Services.MicrosoftGraph
///
public class MicrosoftGraphService
{
+ private readonly SemaphoreSlim _readLock = new SemaphoreSlim(1, 1);
+
///
/// Gets or sets Authentication instance.
///
internal MicrosoftGraphAuthenticationHelper Authentication { get; set; }
+ ///
+ /// Event raised when user logs in our out.
+ ///
+ public event EventHandler IsAuthenticatedChanged;
+
///
/// Gets or sets store a reference to an instance of the underlying data provider.
///
@@ -49,10 +57,27 @@ public class MicrosoftGraphService
///
protected bool IsInitialized { get; set; }
+ private bool _isAuthenticated;
+
///
/// Gets or sets a value indicating whether user is connected.
///
- protected bool IsConnected { get; set; }
+ public bool IsAuthenticated
+ {
+ get
+ {
+ return _isAuthenticated;
+ }
+
+ protected set
+ {
+ if (_isAuthenticated != value)
+ {
+ _isAuthenticated = value;
+ IsAuthenticatedChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+ }
///
/// Gets or sets AppClientId.
@@ -175,6 +200,10 @@ public virtual Task Logout()
{
throw new InvalidOperationException("Microsoft Graph not initialized.");
}
+
+ IsAuthenticated = false;
+ User = null;
+
#if WINRT
var authenticationModel = AuthenticationModel.ToString();
return Authentication.LogoutAsync(authenticationModel);
@@ -190,7 +219,6 @@ public virtual Task Logout()
/// Returns success or failure of login attempt.
public virtual async Task LoginAsync(string loginHint = null)
{
- IsConnected = false;
if (!IsInitialized)
{
throw new InvalidOperationException("Microsoft Graph not initialized.");
@@ -213,11 +241,10 @@ public virtual async Task LoginAsync(string loginHint = null)
if (string.IsNullOrEmpty(accessToken))
{
- return IsConnected;
+ IsAuthenticated = false;
+ return IsAuthenticated;
}
- IsConnected = true;
-
#if WINRT
User = new MicrosoftGraphUserService(GraphProvider);
#else
@@ -239,7 +266,85 @@ public virtual async Task LoginAsync(string loginHint = null)
User.InitializeEvent();
}
- return IsConnected;
+ IsAuthenticated = true;
+ return IsAuthenticated;
+ }
+
+ ///
+ /// Tries to log in user if not already loged in
+ ///
+ /// true if service is already loged in
+ internal async Task TryLoginAsync()
+ {
+ if (!IsInitialized)
+ {
+ return false;
+ }
+
+ if (IsAuthenticated)
+ {
+ return true;
+ }
+
+ try
+ {
+ await _readLock.WaitAsync();
+ await LoginAsync();
+ }
+ catch (MsalServiceException ex)
+ {
+ // Swallow error in case of authentication cancellation.
+ if (ex.ErrorCode != "authentication_canceled"
+ && ex.ErrorCode != "access_denied")
+ {
+ throw ex;
+ }
+ }
+ finally
+ {
+ _readLock.Release();
+ }
+
+ return IsAuthenticated;
+ }
+
+ internal async Task ConnectForAnotherUserAsync()
+ {
+ if (!IsInitialized)
+ {
+ throw new InvalidOperationException("Microsoft Graph not initialized.");
+ }
+
+ try
+ {
+ var publicClientApplication = new PublicClientApplication(AppClientId);
+ AuthenticationResult result = await publicClientApplication.AcquireTokenAsync(DelegatedPermissionScopes);
+
+ var signedUser = result.User;
+
+ foreach (var user in publicClientApplication.Users)
+ {
+ if (user.Identifier != signedUser.Identifier)
+ {
+ publicClientApplication.Remove(user);
+ }
+ }
+
+ await LoginAsync();
+
+ return true;
+ }
+ catch (MsalServiceException ex)
+ {
+ // Swallow error in case of authentication cancellation.
+ if (ex.ErrorCode != "authentication_canceled"
+ && ex.ErrorCode != "access_denied")
+ {
+ throw ex;
+ }
+ }
+
+ return false;
}
///
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/App.xaml b/Microsoft.Toolkit.Uwp.SampleApp/App.xaml
index 0309871e102..3d8f3de563e 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/App.xaml
+++ b/Microsoft.Toolkit.Uwp.SampleApp/App.xaml
@@ -1,17 +1,17 @@
+ xmlns:toolkitControls="using:Microsoft.Toolkit.Uwp.UI.Controls"
+ RequestedTheme="Light"
+ RequiresPointerMode="Auto">
@@ -26,7 +26,8 @@
#FFFF0000
#3750D1
-
+
-
+
+ Color="{StaticResource Brand-Color}" />
-
+
-
+ HorizontalScrollBarVisibility="Auto"
+ HorizontalScrollMode="Auto">
+ Padding="10"
+ FontFamily="Consolas" />
-
-
-
+ ShadowOpacity="0.6"
+ Visibility="Collapsed">
+
-
+
-
+
-
-
+
+
-
-
+
@@ -735,6 +773,7 @@
+ TabIndex="1">
-
-
+
+
-
-
+
+
-
+
@@ -770,24 +816,25 @@
+ Text="{TemplateBinding Title}" />
-
+
-
+
+ TabIndex="2">
@@ -839,38 +890,47 @@
-
+
-
@@ -972,7 +1055,7 @@
-
+
@@ -995,7 +1078,8 @@
-
-
+
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AadLogin/SignInEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AadLogin/SignInEventArgs.cs
new file mode 100644
index 00000000000..a038a7ba6ef
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AadLogin/SignInEventArgs.cs
@@ -0,0 +1,34 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Microsoft.Graph;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// Arguments relating to a sign-in event of Aadlogin control
+ ///
+ public class SignInEventArgs
+ {
+ internal SignInEventArgs()
+ {
+ }
+
+ ///
+ /// Gets the graph service client with authorized token.
+ ///
+ public GraphServiceClient GraphClient
+ {
+ get; internal set;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/folder.svg b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/folder.svg
new file mode 100644
index 00000000000..bd9a64876bd
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/folder.svg
@@ -0,0 +1,34 @@
+
+
+
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/genericfile.png b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/genericfile.png
new file mode 100644
index 00000000000..bf43bc2b680
Binary files /dev/null and b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/genericfile.png differ
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/person.png b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/person.png
new file mode 100644
index 00000000000..35668622a7d
Binary files /dev/null and b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/person.png differ
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/photo.png b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/photo.png
new file mode 100644
index 00000000000..ca21eb5c049
Binary files /dev/null and b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/photo.png differ
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Microsoft.Toolkit.Uwp.UI.Controls.Graph.csproj b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Microsoft.Toolkit.Uwp.UI.Controls.Graph.csproj
new file mode 100644
index 00000000000..2974c91533a
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Microsoft.Toolkit.Uwp.UI.Controls.Graph.csproj
@@ -0,0 +1,42 @@
+
+
+
+ uap10.0
+ Windows Community Toolkit Graph Controls
+ This library provides Microsoft Graph XAML controls .It is part of the Windows Community Toolkit.
+ UWP Toolkit Windows Controls Microsoft Graph AadLogin ProfileCard PeoplePicker SharePointFiles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Events.cs
new file mode 100644
index 00000000000..ca13e44adb6
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Events.cs
@@ -0,0 +1,152 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Graph;
+using Microsoft.Toolkit.Services.MicrosoftGraph;
+using Microsoft.Toolkit.Uwp.UI.Extensions;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// Defines the events for the control.
+ ///
+ public partial class PeoplePicker : Control
+ {
+ private static void AllowMultiplePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var control = d as PeoplePicker;
+ if (!control.AllowMultiple)
+ {
+ control.Selections.Clear();
+ control.RaiseSelectionChanged();
+ control._searchBox.Text = string.Empty;
+ }
+ }
+
+ private void ClearAndHideSearchResultListBox()
+ {
+ SearchResultList.Clear();
+ _searchResultListBox.Visibility = Visibility.Collapsed;
+ }
+
+ private async void SearchBox_OnTextChanged(object sender, TextChangedEventArgs e)
+ {
+ var textboxSender = (TextBox)sender;
+ string searchText = textboxSender.Text.Trim();
+ if (string.IsNullOrWhiteSpace(searchText))
+ {
+ ClearAndHideSearchResultListBox();
+ return;
+ }
+
+ IsLoading = true;
+ try
+ {
+ var graphService = MicrosoftGraphService.Instance;
+ await graphService.TryLoginAsync();
+ GraphServiceClient graphClient = graphService.GraphProvider;
+
+ if (graphClient != null)
+ {
+ var options = new List
+ {
+ new QueryOption("$search", searchText)
+ };
+ IUserPeopleCollectionPage peopleList = await graphClient.Me.People.Request(options).GetAsync();
+
+ if (peopleList.Any())
+ {
+ List searchResult = peopleList.Where(
+ u => !string.IsNullOrWhiteSpace(u.UserPrincipalName)).ToList();
+
+ // Remove all selected items
+ foreach (Person selectedItem in Selections)
+ {
+ searchResult.RemoveAll(u => u.UserPrincipalName == selectedItem.UserPrincipalName);
+ }
+
+ SearchResultList.Clear();
+ var result = SearchResultLimit > 0
+ ? searchResult.Take(SearchResultLimit).ToList()
+ : searchResult;
+ foreach (var item in result)
+ {
+ SearchResultList.Add(item);
+ }
+
+ _searchResultListBox.Visibility = Visibility.Visible;
+ }
+ else
+ {
+ ClearAndHideSearchResultListBox();
+ }
+ }
+ }
+ catch (Exception)
+ {
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+
+ private void SearchResultListBox_OnSelectionChanged(object sender, Windows.UI.Xaml.Controls.SelectionChangedEventArgs e)
+ {
+#pragma warning disable SA1119 // Statement must not use unnecessary parenthesis
+ if (!((sender as ListBox)?.SelectedItem is Person person))
+#pragma warning restore SA1119 // Statement must not use unnecessary parenthesis
+ {
+ return;
+ }
+
+ if (!AllowMultiple && Selections.Any())
+ {
+ Selections.Clear();
+ Selections.Add(person);
+ }
+ else
+ {
+ Selections.Add(person);
+ }
+
+ RaiseSelectionChanged();
+
+ _searchBox.Text = string.Empty;
+ }
+
+ private void SelectionsListBox_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
+ {
+ var elem = e.OriginalSource as FrameworkElement;
+
+ var removeButton = elem.FindAscendantByName("PersonRemoveButton");
+ if (removeButton != null)
+ {
+ if (removeButton.Tag is Person item)
+ {
+ Selections.Remove(item);
+ RaiseSelectionChanged();
+ }
+ }
+ }
+
+ private void RaiseSelectionChanged()
+ {
+ this.SelectionChanged?.Invoke(this, new PeopleSelectionChangedEventArgs(this.Selections));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Properties.cs
new file mode 100644
index 00000000000..88223819c99
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Properties.cs
@@ -0,0 +1,147 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Collections.ObjectModel;
+using Microsoft.Graph;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// Defines the properties for the control.
+ ///
+ public partial class PeoplePicker : Control
+ {
+ ///
+ /// File is selected
+ ///
+ public event EventHandler SelectionChanged;
+
+ ///
+ /// Gets required delegated permissions for the control
+ ///
+ public static string[] RequiredDelegatedPermissions
+ {
+ get
+ {
+ return new string[] { "User.Read", "User.ReadBasic.All" };
+ }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty AllowMultipleProperty =
+ DependencyProperty.Register(
+ nameof(AllowMultiple),
+ typeof(bool),
+ typeof(PeoplePicker),
+ new PropertyMetadata(true, AllowMultiplePropertyChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty SearchResultLimitProperty =
+ DependencyProperty.Register(
+ nameof(SearchResultLimit),
+ typeof(int),
+ typeof(PeoplePicker),
+ null);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty PlaceholderTextProperty =
+ DependencyProperty.Register(
+ nameof(PlaceholderText),
+ typeof(string),
+ typeof(PeoplePicker),
+ new PropertyMetadata("Enter keywords to search people"));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty SelectionsProperty =
+ DependencyProperty.Register(
+ nameof(Selections),
+ typeof(ObservableCollection),
+ typeof(PeoplePicker),
+ null);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ internal static readonly DependencyProperty SearchResultListProperty =
+ DependencyProperty.Register(
+ nameof(SearchResultList),
+ typeof(ObservableCollection),
+ typeof(PeoplePicker),
+ null);
+
+ private static readonly DependencyProperty IsLoadingProperty =
+ DependencyProperty.Register(
+ nameof(IsLoading),
+ typeof(bool),
+ typeof(PeoplePicker),
+ null);
+
+ ///
+ /// Gets or sets a value indicating whether multiple people can be selected
+ ///
+ public bool AllowMultiple
+ {
+ get => (bool)GetValue(AllowMultipleProperty);
+ set => SetValue(AllowMultipleProperty, value);
+ }
+
+ ///
+ /// Gets or sets the max person returned in the search results
+ ///
+ public int SearchResultLimit
+ {
+ get => (int)GetValue(SearchResultLimitProperty);
+ set => SetValue(SearchResultLimitProperty, value);
+ }
+
+ ///
+ /// Gets or sets the text to be displayed when no user is selected
+ ///
+ public string PlaceholderText
+ {
+ get => (string)GetValue(PlaceholderTextProperty);
+ set => SetValue(PlaceholderTextProperty, value);
+ }
+
+ ///
+ /// Gets or sets the selected person list.
+ ///
+ public ObservableCollection Selections
+ {
+ get => (ObservableCollection)GetValue(SelectionsProperty);
+ set => SetValue(SelectionsProperty, value);
+ }
+
+ internal ObservableCollection SearchResultList
+ {
+ get => (ObservableCollection)GetValue(SearchResultListProperty);
+ set => SetValue(SearchResultListProperty, value);
+ }
+
+ private bool IsLoading
+ {
+ get => (bool)GetValue(IsLoadingProperty);
+ set => SetValue(IsLoadingProperty, value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.cs
new file mode 100644
index 00000000000..061e15b7a95
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.cs
@@ -0,0 +1,75 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Collections.ObjectModel;
+using Microsoft.Graph;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// The PeoplePicker Control is a simple control that allows for selection of one or more users from an organizational AD.
+ ///
+ [TemplatePart(Name = SearchBoxPartName, Type = typeof(TextBox))]
+ [TemplatePart(Name = SearchResultListBoxPartName, Type = typeof(ListBox))]
+ [TemplatePart(Name = SelectionsListBoxPartName, Type = typeof(ListBox))]
+ public partial class PeoplePicker : Control
+ {
+ private const string SearchBoxPartName = "SearchBox";
+ private const string SearchResultListBoxPartName = "SearchResultListBox";
+ private const string SelectionsListBoxPartName = "SelectionsListBox";
+
+ private TextBox _searchBox;
+ private ListBox _searchResultListBox;
+ private ListBox _selectionsListBox;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PeoplePicker()
+ {
+ DefaultStyleKey = typeof(PeoplePicker);
+ }
+
+ ///
+ /// Called when applying the control template.
+ ///
+ protected override void OnApplyTemplate()
+ {
+ IsLoading = false;
+
+ _searchBox = GetTemplateChild(SearchBoxPartName) as TextBox;
+ _searchResultListBox = GetTemplateChild(SearchResultListBoxPartName) as ListBox;
+ _selectionsListBox = GetTemplateChild(SelectionsListBoxPartName) as ListBox;
+
+ SearchResultList = new ObservableCollection();
+ Selections = Selections ?? new ObservableCollection();
+ if (_searchBox != null)
+ {
+ _searchBox.TextChanged += SearchBox_OnTextChanged;
+ }
+
+ if (_searchResultListBox != null)
+ {
+ _searchResultListBox.SelectionChanged += SearchResultListBox_OnSelectionChanged;
+ }
+
+ if (_selectionsListBox != null)
+ {
+ _selectionsListBox.Tapped += SelectionsListBox_Tapped;
+ }
+
+ base.OnApplyTemplate();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.xaml
new file mode 100644
index 00000000000..2e2195a40af
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.xaml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+ selected
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeopleSelectionChangedEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeopleSelectionChangedEventArgs.cs
new file mode 100644
index 00000000000..d5d203acb83
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeopleSelectionChangedEventArgs.cs
@@ -0,0 +1,35 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using Microsoft.Graph;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ using System.Collections.ObjectModel;
+
+ ///
+ /// Arguments relating to the people selected event of control
+ ///
+ public class PeopleSelectionChangedEventArgs
+ {
+ ///
+ /// Gets selected file
+ ///
+ public ObservableCollection Selections { get; private set; }
+
+ internal PeopleSelectionChangedEventArgs(ObservableCollection selections)
+ {
+ Selections = selections;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Events.cs
new file mode 100644
index 00000000000..bcca255731b
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Events.cs
@@ -0,0 +1,48 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Microsoft.Toolkit.Services.MicrosoftGraph;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// Defines the events for the control.
+ ///
+ public partial class ProfileCard : Control
+ {
+ private static void OnUserIdPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ => (d as ProfileCard).FetchUserInfo();
+
+ private static void OnDisplayModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var profileCard = d as ProfileCard;
+ ProfileCardItem profileItem = profileCard.CurrentProfileItem.Clone();
+ profileItem.DisplayMode = (ViewType)e.NewValue;
+ profileCard.CurrentProfileItem = profileItem;
+ }
+
+ private static void OnDefaultValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var profileCard = d as ProfileCard;
+ var graphService = MicrosoftGraphService.Instance;
+
+ if (!graphService.IsAuthenticated
+ || string.IsNullOrEmpty(profileCard.UserId)
+ || profileCard.UserId.Equals("Invalid UserId"))
+ {
+ profileCard.InitUserProfile();
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Properties.cs
new file mode 100644
index 00000000000..8387902c0f8
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Properties.cs
@@ -0,0 +1,155 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media.Imaging;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// Defines the properties for the control.
+ ///
+ public partial class ProfileCard : Control
+ {
+ ///
+ /// Gets required delegated permissions for the control
+ ///
+ public static string[] RequiredDelegatedPermissions
+ {
+ get
+ {
+ return new string[] { "User.Read", "User.ReadBasic.All" };
+ }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty UserIdProperty = DependencyProperty.Register(
+ nameof(UserId),
+ typeof(string),
+ typeof(ProfileCard),
+ new PropertyMetadata(string.Empty, OnUserIdPropertyChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register(
+ nameof(DisplayMode),
+ typeof(ViewType),
+ typeof(ProfileCard),
+ new PropertyMetadata(ViewType.PictureOnly, OnDisplayModePropertyChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty DefaultImageProperty = DependencyProperty.Register(
+ nameof(DefaultImage),
+ typeof(BitmapImage),
+ typeof(ProfileCard),
+ new PropertyMetadata(null, OnDefaultValuePropertyChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty LargeProfileTitleDefaultTextProperty = DependencyProperty.Register(
+ nameof(LargeProfileTitleDefaultText),
+ typeof(string),
+ typeof(ProfileCard),
+ new PropertyMetadata(string.Empty, OnDefaultValuePropertyChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty LargeProfileMailDefaultTextProperty = DependencyProperty.Register(
+ nameof(LargeProfileMailDefaultText),
+ typeof(string),
+ typeof(ProfileCard),
+ new PropertyMetadata(string.Empty, OnDefaultValuePropertyChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty NormalMailDefaultTextProperty = DependencyProperty.Register(
+ nameof(NormalMailDefaultText),
+ typeof(string),
+ typeof(ProfileCard),
+ new PropertyMetadata(string.Empty, OnDefaultValuePropertyChanged));
+
+ internal static readonly DependencyProperty CurrentProfileItemProperty = DependencyProperty.Register(
+ nameof(CurrentProfileItem),
+ typeof(ProfileCardItem),
+ typeof(ProfileCard),
+ new PropertyMetadata(new ProfileCardItem()));
+
+ ///
+ /// Gets or sets user unique identifier.
+ ///
+ public string UserId
+ {
+ get { return ((string)GetValue(UserIdProperty))?.Trim(); }
+ set { SetValue(UserIdProperty, value?.Trim()); }
+ }
+
+ ///
+ /// Gets or sets the visual layout of the control. Default is PictureOnly.
+ ///
+ public ViewType DisplayMode
+ {
+ get { return (ViewType)GetValue(DisplayModeProperty); }
+ set { SetValue(DisplayModeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the default image when no user is signed in.
+ ///
+ public BitmapImage DefaultImage
+ {
+ get { return (BitmapImage)GetValue(DefaultImageProperty); }
+ set { SetValue(DefaultImageProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the default title text in LargeProfilePhotoLeft mode or LargeProfilePhotoRight mode when no user is signed in.
+ ///
+ public string LargeProfileTitleDefaultText
+ {
+ get { return (string)GetValue(LargeProfileTitleDefaultTextProperty); }
+ set { SetValue(LargeProfileTitleDefaultTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the default secondary mail text in LargeProfilePhotoLeft mode or LargeProfilePhotoRight mode when no user is signed in.
+ ///
+ public string LargeProfileMailDefaultText
+ {
+ get { return (string)GetValue(LargeProfileMailDefaultTextProperty); }
+ set { SetValue(LargeProfileMailDefaultTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the default mail text in EmailOnly mode when no user is signed in.
+ ///
+ public string NormalMailDefaultText
+ {
+ get { return (string)GetValue(NormalMailDefaultTextProperty); }
+ set { SetValue(NormalMailDefaultTextProperty, value); }
+ }
+
+ internal ProfileCardItem CurrentProfileItem
+ {
+ get { return (ProfileCardItem)GetValue(CurrentProfileItemProperty); }
+ set { SetValue(CurrentProfileItemProperty, value); }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.cs
new file mode 100644
index 00000000000..ccff427b892
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.cs
@@ -0,0 +1,138 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.IO;
+using Microsoft.Graph;
+using Microsoft.Toolkit.Services.MicrosoftGraph;
+using Windows.UI.Core;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media.Imaging;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// The Profile Card control is a simple way to display a user in multiple different formats and mixes of name/image/e-mail.
+ ///
+ public partial class ProfileCard : Control
+ {
+ private static readonly BitmapImage PersonPhoto = new BitmapImage(new Uri("ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/person.png"));
+ private ContentControl _contentPresenter;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProfileCard()
+ {
+ DefaultStyleKey = typeof(ProfileCard);
+ }
+
+ ///
+ /// Override default OnApplyTemplate to initialize child controls
+ ///
+ protected override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ _contentPresenter = GetTemplateChild("ContentPresenter") as ContentControl;
+ if (_contentPresenter != null)
+ {
+ _contentPresenter.ContentTemplateSelector = new ProfileDisplayModeTemplateSelector(_contentPresenter);
+ }
+
+ FetchUserInfo();
+ }
+
+ private async void FetchUserInfo()
+ {
+ if (string.IsNullOrEmpty(UserId) || UserId.Equals("Invalid UserId"))
+ {
+ InitUserProfile();
+ }
+ else
+ {
+ var graphService = MicrosoftGraphService.Instance;
+ if (!(await graphService.TryLoginAsync()))
+ {
+ return;
+ }
+
+ try
+ {
+ var user = await graphService.GraphProvider.Users[UserId].Request().GetAsync();
+ var profileItem = new ProfileCardItem()
+ {
+ NormalMail = user.Mail,
+ LargeProfileTitle = user.DisplayName,
+ LargeProfileMail = user.Mail,
+ DisplayMode = DisplayMode
+ };
+
+ if (string.IsNullOrEmpty(user.Mail))
+ {
+ profileItem.UserPhoto = DefaultImage ?? PersonPhoto;
+ }
+ else
+ {
+ try
+ {
+ using (Stream photoStream = await graphService.GraphProvider.Users[UserId].Photo.Content.Request().GetAsync())
+ using (var ras = photoStream.AsRandomAccessStream())
+ {
+ var bitmapImage = new BitmapImage();
+ await bitmapImage.SetSourceAsync(ras);
+ profileItem.UserPhoto = bitmapImage;
+ }
+ }
+ catch
+ {
+ // Swallow error in case of no photo found
+ profileItem.UserPhoto = DefaultImage ?? PersonPhoto;
+ }
+ }
+
+ await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
+ {
+ CurrentProfileItem = profileItem;
+ });
+ }
+ catch (ServiceException ex)
+ {
+ // Swallow error in case of no user id found
+ if (!ex.Error.Code.Equals("Request_ResourceNotFound"))
+ {
+ throw;
+ }
+
+ await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
+ {
+ UserId = "Invalid UserId";
+ });
+ }
+ }
+ }
+
+ private void InitUserProfile()
+ {
+ var profileItem = new ProfileCardItem()
+ {
+ UserPhoto = DefaultImage ?? PersonPhoto,
+ NormalMail = NormalMailDefaultText ?? string.Empty,
+ LargeProfileTitle = LargeProfileTitleDefaultText ?? string.Empty,
+ LargeProfileMail = LargeProfileMailDefaultText ?? string.Empty,
+ DisplayMode = DisplayMode
+ };
+
+ CurrentProfileItem = profileItem;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.xaml
new file mode 100644
index 00000000000..e4a2ca98388
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.xaml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCardItem.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCardItem.cs
new file mode 100644
index 00000000000..6439d9df0ab
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCardItem.cs
@@ -0,0 +1,34 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Windows.UI.Xaml.Media.Imaging;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ internal class ProfileCardItem
+ {
+ public string NormalMail { get; set; }
+
+ public string LargeProfileTitle { get; set; }
+
+ public string LargeProfileMail { get; set; }
+
+ public BitmapImage UserPhoto { get; set; }
+
+ public ViewType DisplayMode { get; set; }
+
+ public ProfileCardItem Clone()
+ {
+ return (ProfileCardItem)MemberwiseClone();
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileDisplayModeTemplateSelector.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileDisplayModeTemplateSelector.cs
new file mode 100644
index 00000000000..c00cc33fdd0
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileDisplayModeTemplateSelector.cs
@@ -0,0 +1,63 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ internal class ProfileDisplayModeTemplateSelector : DataTemplateSelector
+ {
+ private ContentControl _contentPresenter;
+
+ internal ProfileDisplayModeTemplateSelector(ContentControl contentPresenter)
+ {
+ _contentPresenter = contentPresenter;
+ }
+
+ private DataTemplate SelectItemTemplate(object item)
+ {
+ DataTemplate dataTemplate = null;
+
+ if (item != null && item is ProfileCardItem profileItem)
+ {
+ switch (profileItem.DisplayMode)
+ {
+ case ViewType.EmailOnly:
+ dataTemplate = _contentPresenter.Resources["EmailOnly"] as DataTemplate;
+ break;
+ case ViewType.PictureOnly:
+ dataTemplate = _contentPresenter.Resources["PictureOnly"] as DataTemplate;
+ break;
+ case ViewType.LargeProfilePhotoLeft:
+ dataTemplate = _contentPresenter.Resources["LargeProfilePhotoLeft"] as DataTemplate;
+ break;
+ case ViewType.LargeProfilePhotoRight:
+ dataTemplate = _contentPresenter.Resources["LargeProfilePhotoRight"] as DataTemplate;
+ break;
+ }
+ }
+
+ return dataTemplate;
+ }
+
+ protected override DataTemplate SelectTemplateCore(object item)
+ {
+ return SelectItemTemplate(item);
+ }
+
+ protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
+ {
+ return SelectItemTemplate(item);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ViewType.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ViewType.cs
new file mode 100644
index 00000000000..51b4542b171
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ViewType.cs
@@ -0,0 +1,40 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// The visual layout of the control. Default is PictureOnly.
+ ///
+ public enum ViewType
+ {
+ ///
+ /// Only the user photo is shown.
+ ///
+ PictureOnly = 0,
+
+ ///
+ /// Only the user email is shown.
+ ///
+ EmailOnly = 1,
+
+ ///
+ /// A basic user profile is shown, and the user photo is place on the left side.
+ ///
+ LargeProfilePhotoLeft = 2,
+
+ ///
+ /// A basic user profile is shown, and the user photo is place on the right side.
+ ///
+ LargeProfilePhotoRight = 3
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000000..d468edfac79
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/AssemblyInfo.cs
@@ -0,0 +1,18 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+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: InternalsVisibleTo("UnitTests")]
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/Microsoft.Windows.Toolkit.UI.Controls.Graph.rd.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/Microsoft.Windows.Toolkit.UI.Controls.Graph.rd.xml
new file mode 100644
index 00000000000..9ea5a951ee2
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/Microsoft.Windows.Toolkit.UI.Controls.Graph.rd.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/DetailPaneDisplayMode.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/DetailPaneDisplayMode.cs
new file mode 100644
index 00000000000..f50b416572f
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/DetailPaneDisplayMode.cs
@@ -0,0 +1,40 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// Determines how file details panel is displayed in the control.
+ ///
+ public enum DetailPaneDisplayMode
+ {
+ ///
+ /// Hide show DetailPane
+ ///
+ Disabled,
+
+ ///
+ /// Show DetailPane aside
+ ///
+ Side,
+
+ ///
+ /// Show DetailPane at bottom
+ ///
+ Bottom,
+
+ ///
+ /// Show DetailPane over list
+ ///
+ Full
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/DriveItemIconConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/DriveItemIconConverter.cs
new file mode 100644
index 00000000000..e92ad39d358
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/DriveItemIconConverter.cs
@@ -0,0 +1,68 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using Microsoft.Graph;
+using Windows.UI.Xaml.Data;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// Get icon of DriveItem
+ ///
+ internal class DriveItemIconConverter : IValueConverter
+ {
+ private static readonly string OfficeIcon = "https://static2.sharepointonline.com/files/fabric/assets/brand-icons/document/png/{0}_32x1_5.png";
+ private static readonly string LocalIcon = "ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/{0}";
+
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ DriveItem driveItem = value as DriveItem;
+
+ if (driveItem.Folder != null)
+ {
+ return string.Format(LocalIcon, "folder.svg");
+ }
+ else if (driveItem.File != null)
+ {
+ if (driveItem.File.MimeType.StartsWith("image"))
+ {
+ return string.Format(LocalIcon, "photo.png");
+ }
+ else if (driveItem.File.MimeType.StartsWith("application/vnd.openxmlformats-officedocument"))
+ {
+ int index = driveItem.Name.LastIndexOf('.');
+ if (index != -1)
+ {
+ string ext = driveItem.Name.Substring(index + 1);
+ return string.Format(OfficeIcon, ext);
+ }
+ }
+ }
+ else if (driveItem.Package != null)
+ {
+ switch (driveItem.Package.Type)
+ {
+ case "oneNote":
+ return string.Format(OfficeIcon, "one");
+ }
+ }
+
+ return string.Format(LocalIcon, "genericfile.png");
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ return null;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/FileSelectedEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/FileSelectedEventArgs.cs
new file mode 100644
index 00000000000..a0ffa01dc0b
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/FileSelectedEventArgs.cs
@@ -0,0 +1,33 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using Microsoft.Graph;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// Arguments relating to a file selected event of SharePointFiles control
+ ///
+ public class FileSelectedEventArgs
+ {
+ ///
+ /// Gets selected file
+ ///
+ public DriveItem FileSelected { get; private set; }
+
+ internal FileSelectedEventArgs(DriveItem fileSelected)
+ {
+ FileSelected = fileSelected;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/FileSizeConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/FileSizeConverter.cs
new file mode 100644
index 00000000000..f392c1831fa
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/FileSizeConverter.cs
@@ -0,0 +1,32 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using Microsoft.Toolkit.Extensions;
+using Windows.UI.Xaml.Data;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ internal class FileSizeConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ long size = (long)value;
+ return size.ToFileSizeString();
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/Int64Extensions.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/Int64Extensions.cs
new file mode 100644
index 00000000000..bac3a7fa3fb
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/Int64Extensions.cs
@@ -0,0 +1,57 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ ///
+ /// All common long extensions should go here
+ ///
+ internal static class Int64Extensions
+ {
+ ///
+ /// Translate numeric file size to string format.
+ ///
+ /// file size in bytes.
+ /// Returns file size string.
+ public static string ToFileSizeString(this long size)
+ {
+ if (size < 1024)
+ {
+ return size.ToString("F0") + " bytes";
+ }
+ else if ((size >> 10) < 1024)
+ {
+ return (size / (float)1024).ToString("F1") + " KB";
+ }
+ else if ((size >> 20) < 1024)
+ {
+ return ((size >> 10) / (float)1024).ToString("F1") + " MB";
+ }
+ else if ((size >> 30) < 1024)
+ {
+ return ((size >> 20) / (float)1024).ToString("F1") + " GB";
+ }
+ else if ((size >> 40) < 1024)
+ {
+ return ((size >> 30) / (float)1024).ToString("F1") + " TB";
+ }
+ else if ((size >> 50) < 1024)
+ {
+ return ((size >> 40) / (float)1024).ToString("F1") + " PB";
+ }
+ else
+ {
+ return ((size >> 50) / (float)1024).ToString("F0") + " EB";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.Constants.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.Constants.cs
new file mode 100644
index 00000000000..ec2dd0d5df4
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.Constants.cs
@@ -0,0 +1,135 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// The SharePointFiles Control displays a simple list of SharePoint Files.
+ ///
+ public partial class SharePointFileList
+ {
+ ///
+ /// Key of the VisualStateGroup to control nav buttons
+ ///
+ private const string NavStates = "NavStates";
+
+ ///
+ /// Key of the VisualState when display folder in readonly mode
+ ///
+ private const string NavStatesFolderReadonly = "FolderReadOnly";
+
+ ///
+ /// Key of the VisualState when display folder in edit mode
+ ///
+ private const string NavStatesFolderEdit = "FolderEdit";
+
+ ///
+ /// Key of the VisualState when display file in readonly mode
+ ///
+ private const string NavStatesFileReadonly = "FileReadonly";
+
+ ///
+ /// Key of the VisualState when display file in edit mode
+ ///
+ private const string NavStatesFileEdit = "FileEdit";
+
+ ///
+ /// Key of the VisualStateGroup to control uploading status
+ ///
+ private const string UploadStatus = "UploadStatus";
+
+ ///
+ /// Key of the VisualState when not uploading files
+ ///
+ private const string UploadStatusNotUploading = "NotUploading";
+
+ ///
+ /// Key of the VisualState when uploading files
+ ///
+ private const string UploadStatusUploading = "Uploading";
+
+ ///
+ /// Key of the VisualState when uploading error occurs
+ ///
+ private const string UploadStatusError = "Error";
+
+ ///
+ /// Key of the VisualStateGroup to control detail pane
+ ///
+ private const string DetailPaneStates = "DetailPaneStates";
+
+ ///
+ /// Key of the VisualState when detail pane is hidden
+ ///
+ private const string DetailPaneStatesHide = "Hide";
+
+ ///
+ /// Key of the VisualState when detail pane is at right side
+ ///
+ private const string DetailPaneStatesSide = "Side";
+
+ ///
+ /// Key of the VisualState when detail pane is at bottom
+ ///
+ private const string DetailPaneStatesBottom = "Bottom";
+
+ ///
+ /// Key of the VisualState when detail pane is in full mode
+ ///
+ private const string DetailPaneStatesFull = "Full";
+
+ ///
+ /// Key of the ListView that contains file list
+ ///
+ private const string ControlFileList = "list";
+
+ ///
+ /// Key of the back button that contains file list
+ ///
+ private const string ControlBack = "back";
+
+ ///
+ /// Key of the upload button that contains file list
+ ///
+ private const string ControlUpload = "upload";
+
+ ///
+ /// Key of the share button that contains file list
+ ///
+ private const string ControlShare = "share";
+
+ ///
+ /// Key of the download button that contains file list
+ ///
+ private const string ControlDownload = "download";
+
+ ///
+ /// Key of the delete button that contains file list
+ ///
+ private const string ControlDelete = "delete";
+
+ ///
+ /// Key of the error button that contains file list
+ ///
+ private const string ControlError = "error";
+
+ ///
+ /// Key of the cancel button that contains file list
+ ///
+ private const string ControlCancel = "cancel";
+
+ ///
+ /// Key of the has more button that contains file list
+ ///
+ private const string ControlLoadMore = "hasMore";
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.Events.cs
new file mode 100644
index 00000000000..4b0df39a5f6
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.Events.cs
@@ -0,0 +1,301 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Graph;
+using Microsoft.Toolkit.Services.MicrosoftGraph;
+using Windows.ApplicationModel.DataTransfer;
+using Windows.Storage;
+using Windows.Storage.Pickers;
+using Windows.UI.Popups;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Media.Imaging;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// The SharePointFiles Control displays a simple list of SharePoint Files.
+ ///
+ public partial class SharePointFileList
+ {
+ private static async void DriveUrlPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (MicrosoftGraphService.Instance.IsAuthenticated)
+ {
+ SharePointFileList control = d as SharePointFileList;
+ await MicrosoftGraphService.Instance.TryLoginAsync();
+ GraphServiceClient graphServiceClient = MicrosoftGraphService.Instance.GraphProvider;
+ if (graphServiceClient != null && !string.IsNullOrWhiteSpace(control.DriveUrl))
+ {
+ if (Uri.IsWellFormedUriString(control.DriveUrl, UriKind.Absolute))
+ {
+ await control.InitDriveAsync(control.DriveUrl);
+ }
+ }
+ }
+ }
+
+ private static void DetailPanePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ SharePointFileList control = d as SharePointFileList;
+ if (control.IsDetailPaneVisible)
+ {
+ control.ShowDetailsPane();
+ }
+ }
+
+ private async void Back_Click(object sender, RoutedEventArgs e)
+ {
+ if (DetailPane == DetailPaneDisplayMode.Full && IsDetailPaneVisible)
+ {
+ IsDetailPaneVisible = false;
+ HideDetailsPane();
+ }
+ else if (_driveItemPath.Count > 1)
+ {
+ _driveItemPath.Pop();
+ string parentItemId = _driveItemPath.Peek().Id;
+ if (_driveItemPath.Count == 1)
+ {
+ BackButtonVisibility = Visibility.Collapsed;
+ }
+
+ UpdateCurrentPath();
+ await LoadFilesAsync(parentItemId);
+ }
+ }
+
+ private async void Upload_Click(object sender, RoutedEventArgs e)
+ {
+ ErrorMessage = string.Empty;
+ FileOpenPicker picker = new FileOpenPicker();
+ picker.FileTypeFilter.Add("*");
+ StorageFile file = await picker.PickSingleFileAsync();
+ if (file != null)
+ {
+ string driveItemId = _driveItemPath.Peek()?.Id;
+ using (Stream inputStream = await file.OpenStreamForReadAsync())
+ {
+ if (inputStream.Length < 1024 * 1024 * 4)
+ {
+ FileUploading++;
+ StatusMessage = string.Format(UploadingFilesMessageTemplate, FileUploading);
+ VisualStateManager.GoToState(this, UploadStatusUploading, false);
+ try
+ {
+ await GraphService.TryLoginAsync();
+ GraphServiceClient graphServiceClient = GraphService.GraphProvider;
+ await graphServiceClient.Drives[_driveId].Items[driveItemId].ItemWithPath(file.Name).Content.Request().PutAsync(inputStream, _cancelUpload.Token);
+ VisualStateManager.GoToState(this, UploadStatusNotUploading, false);
+ FileUploading--;
+ }
+ catch (Exception ex)
+ {
+ FileUploading--;
+ ErrorMessage = ex.Message;
+ VisualStateManager.GoToState(this, UploadStatusError, false);
+ }
+
+ await LoadFilesAsync(driveItemId);
+ }
+ }
+ }
+ }
+
+ private async void Share_Click(object sender, RoutedEventArgs e)
+ {
+ if (_list.SelectedItem is DriveItem driveItem)
+ {
+ try
+ {
+ await GraphService.TryLoginAsync();
+ GraphServiceClient graphServiceClient = GraphService.GraphProvider;
+ Permission link = await graphServiceClient.Drives[_driveId].Items[driveItem.Id].CreateLink("view", "organization").Request().PostAsync();
+ MessageDialog dialog = new MessageDialog(link.Link.WebUrl, ShareLinkCopiedMessage);
+ DataPackage package = new DataPackage();
+ package.SetText(link.Link.WebUrl);
+ Clipboard.SetContent(package);
+ await dialog.ShowAsync();
+ }
+ catch (Exception exception)
+ {
+ MessageDialog dialog = new MessageDialog(exception.Message);
+ await dialog.ShowAsync();
+ }
+ }
+ }
+
+ private async void Download_Click(object sender, RoutedEventArgs e)
+ {
+ if (_list.SelectedItem is DriveItem driveItem)
+ {
+ await GraphService.TryLoginAsync();
+ GraphServiceClient graphServiceClient = GraphService.GraphProvider;
+ FileSavePicker picker = new FileSavePicker();
+ picker.FileTypeChoices.Add(AllFilesMessage, new List() { driveItem.Name.Substring(driveItem.Name.LastIndexOf(".")) });
+ picker.SuggestedFileName = driveItem.Name;
+ picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
+ StorageFile file = await picker.PickSaveFileAsync();
+ if (file != null)
+ {
+ using (Stream inputStream = await graphServiceClient.Drives[_driveId].Items[driveItem.Id].Content.Request().GetAsync())
+ {
+ using (Stream outputStream = await file.OpenStreamForWriteAsync())
+ {
+ await inputStream.CopyToAsync(outputStream);
+ }
+ }
+ }
+ }
+ }
+
+ private async void Delete_Click(object sender, RoutedEventArgs e)
+ {
+ if (_list.SelectedItem is DriveItem driveItem)
+ {
+ MessageDialog confirmDialog = new MessageDialog(DeleteConfirmMessage);
+ confirmDialog.Commands.Add(new UICommand(DeleteConfirmOkMessage, cmd => { }, commandId: 0));
+ confirmDialog.Commands.Add(new UICommand(DeleteConfirmCancelMessage, cmd => { }, commandId: 1));
+
+ confirmDialog.DefaultCommandIndex = 0;
+ confirmDialog.CancelCommandIndex = 1;
+
+ IUICommand result = await confirmDialog.ShowAsync();
+
+ if ((int)result.Id == 0)
+ {
+ await GraphService.TryLoginAsync();
+ GraphServiceClient graphServiceClient = GraphService.GraphProvider;
+ await graphServiceClient.Drives[_driveId].Items[driveItem.Id].Request().DeleteAsync();
+ string driveItemId = _driveItemPath.Peek()?.Id;
+ await LoadFilesAsync(driveItemId);
+ }
+ }
+ }
+
+ private async void ShowErrorDetails_Click(object sender, RoutedEventArgs e)
+ {
+ MessageDialog messageDialog = new MessageDialog(ErrorMessage);
+ await messageDialog.ShowAsync();
+ }
+
+ private async void LoadMore_Click(object sender, RoutedEventArgs e)
+ {
+ await LoadNextPageAsync();
+ }
+
+ private void Cancel_Click(object sender, RoutedEventArgs e)
+ {
+ _cancelUpload.Cancel(false);
+ _cancelUpload.Dispose();
+ _cancelUpload = new CancellationTokenSource();
+ }
+
+ private async void List_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ _cancelGetDetails.Cancel(false);
+ _cancelGetDetails.Dispose();
+ _cancelGetDetails = new CancellationTokenSource();
+ if (_list.SelectedItem is DriveItem driveItem && driveItem.File != null)
+ {
+ try
+ {
+ SelectedFile = driveItem;
+ FileSize = driveItem.Size ?? 0;
+ LastModified = driveItem.LastModifiedDateTime?.LocalDateTime.ToString() ?? string.Empty;
+ if (FileSelected != null)
+ {
+ FileSelected.Invoke(this, new FileSelectedEventArgs(driveItem));
+ }
+
+ ThumbnailImageSource = null;
+ VisualStateManager.GoToState(this, NavStatesFileReadonly, false);
+ await GraphService.TryLoginAsync();
+ GraphServiceClient graphServiceClient = GraphService.GraphProvider;
+ Task taskPermissions = graphServiceClient.Drives[_driveId].Items[driveItem.Id].Permissions.Request().GetAsync(_cancelGetDetails.Token);
+ IDriveItemPermissionsCollectionPage permissions = await taskPermissions;
+ if (!taskPermissions.IsCanceled)
+ {
+ foreach (Permission permission in permissions)
+ {
+ if (permission.Roles.Contains("write") || permission.Roles.Contains("owner"))
+ {
+ VisualStateManager.GoToState(this, NavStatesFileEdit, false);
+ break;
+ }
+ }
+
+ Task taskThumbnails = graphServiceClient.Drives[_driveId].Items[driveItem.Id].Thumbnails.Request().GetAsync(_cancelGetDetails.Token);
+ IDriveItemThumbnailsCollectionPage thumbnails = await taskThumbnails;
+ if (!taskThumbnails.IsCanceled)
+ {
+ ThumbnailSet thumbnailsSet = thumbnails.FirstOrDefault();
+ if (thumbnailsSet != null)
+ {
+ Thumbnail thumbnail = thumbnailsSet.Large;
+ if (thumbnail.Url.Contains("inputFormat=svg"))
+ {
+ SvgImageSource source = new SvgImageSource();
+ using (Stream inputStream = await graphServiceClient.Drives[_driveId].Items[driveItem.Id].Content.Request().GetAsync())
+ {
+ SvgImageSourceLoadStatus status = await source.SetSourceAsync(inputStream.AsRandomAccessStream());
+ if (status == SvgImageSourceLoadStatus.Success)
+ {
+ ThumbnailImageSource = source;
+ }
+ }
+ }
+ else
+ {
+ ThumbnailImageSource = new BitmapImage(new Uri(thumbnail.Url));
+ }
+ }
+ }
+
+ IsDetailPaneVisible = true;
+ ShowDetailsPane();
+ }
+ }
+ catch
+ {
+ }
+ }
+ else
+ {
+ SelectedFile = null;
+ FileSize = 0;
+ LastModified = string.Empty;
+ VisualStateManager.GoToState(this, _pathVisualState, false);
+ IsDetailPaneVisible = false;
+ HideDetailsPane();
+ }
+ }
+
+ private async void List_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ if (e.ClickedItem is DriveItem driveItem && driveItem.Folder != null)
+ {
+ _driveItemPath.Push(driveItem);
+ UpdateCurrentPath();
+ BackButtonVisibility = Visibility.Visible;
+ await LoadFilesAsync(driveItem.Id);
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.Properties.cs
new file mode 100644
index 00000000000..478aa2f014e
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.Properties.cs
@@ -0,0 +1,340 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Microsoft.Graph;
+using Microsoft.Toolkit.Services.MicrosoftGraph;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// The SharePointFiles Control displays a simple list of SharePoint Files.
+ ///
+ public partial class SharePointFileList
+ {
+ ///
+ /// Gets the instance
+ ///
+ public static MicrosoftGraphService GraphService => MicrosoftGraphService.Instance;
+
+ ///
+ /// Gets required delegated permissions for the control
+ ///
+ public static string[] RequiredDelegatedPermissions
+ {
+ get
+ {
+ return new string[] { "User.Read", "Files.ReadWrite.All" };
+ }
+ }
+
+ ///
+ /// Url of OneDrive to display
+ ///
+ public static readonly DependencyProperty DriveUrlProperty =
+ DependencyProperty.Register(
+ nameof(DriveUrl),
+ typeof(string),
+ typeof(SharePointFileList),
+ new PropertyMetadata(string.Empty, DriveUrlPropertyChanged));
+
+ ///
+ /// How details of a file shows
+ ///
+ public static readonly DependencyProperty DetailPaneProperty =
+ DependencyProperty.Register(
+ nameof(DetailPane),
+ typeof(DetailPaneDisplayMode),
+ typeof(SharePointFileList),
+ new PropertyMetadata(DetailPaneDisplayMode.Disabled, DetailPanePropertyChanged));
+
+ ///
+ /// Page size of each request
+ ///
+ public static readonly DependencyProperty PageSizeProperty =
+ DependencyProperty.Register(
+ nameof(PageSize),
+ typeof(int),
+ typeof(SharePointFileList),
+ new PropertyMetadata(20));
+
+ ///
+ /// Share link copied message
+ ///
+ public static readonly DependencyProperty ShareLinkCopiedMessageProperty =
+ DependencyProperty.Register(
+ nameof(ShareLinkCopiedMessage),
+ typeof(string),
+ typeof(SharePointFileList),
+ new PropertyMetadata("Link copied!"));
+
+ ///
+ /// All files message
+ ///
+ public static readonly DependencyProperty AllFilesMessageProperty =
+ DependencyProperty.Register(
+ nameof(AllFilesMessage),
+ typeof(string),
+ typeof(SharePointFileList),
+ new PropertyMetadata("All Files"));
+
+ ///
+ /// Delete confirm message
+ ///
+ public static readonly DependencyProperty DeleteConfirmMessageProperty =
+ DependencyProperty.Register(
+ nameof(DeleteConfirmMessage),
+ typeof(string),
+ typeof(SharePointFileList),
+ new PropertyMetadata("Do you want to delete this file?"));
+
+ ///
+ /// Delete confirm Ok message
+ ///
+ public static readonly DependencyProperty DeleteConfirmOkMessageProperty =
+ DependencyProperty.Register(
+ nameof(DeleteConfirmOkMessage),
+ typeof(string),
+ typeof(SharePointFileList),
+ new PropertyMetadata("OK"));
+
+ ///
+ /// Delete confirm cancel message
+ ///
+ public static readonly DependencyProperty DeleteConfirmCancelMessageProperty =
+ DependencyProperty.Register(
+ nameof(DeleteConfirmCancelMessage),
+ typeof(string),
+ typeof(SharePointFileList),
+ new PropertyMetadata("Cancel"));
+
+ ///
+ /// Uploading files message template
+ ///
+ public static readonly DependencyProperty UploadingFilesMessageTemplateProperty =
+ DependencyProperty.Register(
+ nameof(UploadingFilesMessageTemplate),
+ typeof(string),
+ typeof(SharePointFileList),
+ new PropertyMetadata("Uploading {0} files..."));
+
+ internal static readonly DependencyProperty ThumbnailImageSourceProperty =
+ DependencyProperty.Register(
+ nameof(ThumbnailImageSource),
+ typeof(ImageSource),
+ typeof(SharePointFileList),
+ new PropertyMetadata(null));
+
+ internal static readonly DependencyProperty HasMoreProperty =
+ DependencyProperty.Register(
+ nameof(HasMore),
+ typeof(bool),
+ typeof(SharePointFileList),
+ new PropertyMetadata(false));
+
+ internal static readonly DependencyProperty SelectedFileProperty =
+ DependencyProperty.Register(
+ nameof(SelectedFile),
+ typeof(DriveItem),
+ typeof(SharePointFileList),
+ new PropertyMetadata(null));
+
+ internal static readonly DependencyProperty SizeProperty =
+ DependencyProperty.Register(
+ nameof(FileSize),
+ typeof(long),
+ typeof(SharePointFileList),
+ new PropertyMetadata(0L));
+
+ internal static readonly DependencyProperty LastModifiedProperty =
+ DependencyProperty.Register(
+ nameof(LastModified),
+ typeof(string),
+ typeof(SharePointFileList),
+ null);
+
+ internal static readonly DependencyProperty BackButtonVisibilityProperty =
+ DependencyProperty.Register(
+ nameof(BackButtonVisibility),
+ typeof(Visibility),
+ typeof(SharePointFileList),
+ new PropertyMetadata(Visibility.Collapsed));
+
+ internal static readonly DependencyProperty StatusMessageProperty =
+ DependencyProperty.Register(
+ nameof(StatusMessage),
+ typeof(string),
+ typeof(SharePointFileList),
+ new PropertyMetadata(string.Empty));
+
+ private static readonly DependencyProperty IsDetailPaneVisibleProperty =
+ DependencyProperty.Register(
+ nameof(IsDetailPaneVisible),
+ typeof(bool),
+ typeof(SharePointFileList),
+ new PropertyMetadata(false));
+
+ internal static readonly DependencyProperty CurrentPathProperty =
+ DependencyProperty.Register(
+ nameof(CurrentPath),
+ typeof(string),
+ typeof(SharePointFileList),
+ new PropertyMetadata(string.Empty));
+
+ ///
+ /// Gets or sets drive or SharePoint document library URL to display
+ ///
+ public string DriveUrl
+ {
+ get { return ((string)GetValue(DriveUrlProperty))?.Trim(); }
+ set { SetValue(DriveUrlProperty, value?.Trim()); }
+ }
+
+ ///
+ /// Gets or sets how DetailPane shows
+ ///
+ public DetailPaneDisplayMode DetailPane
+ {
+ get { return (DetailPaneDisplayMode)GetValue(DetailPaneProperty); }
+ set { SetValue(DetailPaneProperty, value); }
+ }
+
+ ///
+ /// Gets or sets page size of each request
+ ///
+ public int PageSize
+ {
+ get { return (int)GetValue(PageSizeProperty); }
+ set { SetValue(PageSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the message when share link copied
+ ///
+ public string ShareLinkCopiedMessage
+ {
+ get { return (string)GetValue(ShareLinkCopiedMessageProperty); }
+ set { SetValue(ShareLinkCopiedMessageProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the label of All Files
+ ///
+ public string AllFilesMessage
+ {
+ get { return (string)GetValue(AllFilesMessageProperty); }
+ set { SetValue(AllFilesMessageProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the message of delete confirm dialog
+ ///
+ public string DeleteConfirmMessage
+ {
+ get { return (string)GetValue(DeleteConfirmMessageProperty); }
+ set { SetValue(DeleteConfirmMessageProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the caption of ok button in delete confirm dialog
+ ///
+ public string DeleteConfirmOkMessage
+ {
+ get { return (string)GetValue(DeleteConfirmOkMessageProperty); }
+ set { SetValue(DeleteConfirmOkMessageProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the caption of cancel button in delete confirm dialog
+ ///
+ public string DeleteConfirmCancelMessage
+ {
+ get { return (string)GetValue(DeleteConfirmCancelMessageProperty); }
+ set { SetValue(DeleteConfirmCancelMessageProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the template of uploading files
+ ///
+ public string UploadingFilesMessageTemplate
+ {
+ get { return (string)GetValue(UploadingFilesMessageTemplateProperty); }
+ set { SetValue(UploadingFilesMessageTemplateProperty, value); }
+ }
+
+ internal bool HasMore
+ {
+ get { return (bool)GetValue(HasMoreProperty); }
+ set { SetValue(HasMoreProperty, value); }
+ }
+
+ internal DriveItem SelectedFile
+ {
+ get { return (DriveItem)GetValue(SelectedFileProperty); }
+ set { SetValue(SelectedFileProperty, value); }
+ }
+
+ internal long FileSize
+ {
+ get { return (long)GetValue(SizeProperty); }
+ set { SetValue(SizeProperty, value); }
+ }
+
+ internal string LastModified
+ {
+ get { return (string)GetValue(LastModifiedProperty); }
+ set { SetValue(LastModifiedProperty, value); }
+ }
+
+ internal ImageSource ThumbnailImageSource
+ {
+ get { return (ImageSource)GetValue(ThumbnailImageSourceProperty); }
+ set { SetValue(ThumbnailImageSourceProperty, value); }
+ }
+
+ internal string StatusMessage
+ {
+ get { return (string)GetValue(StatusMessageProperty); }
+ set { SetValue(StatusMessageProperty, value); }
+ }
+
+ internal Visibility BackButtonVisibility
+ {
+ get { return (Visibility)GetValue(BackButtonVisibilityProperty); }
+ set { SetValue(BackButtonVisibilityProperty, value); }
+ }
+
+ internal string CurrentPath
+ {
+ get { return (string)GetValue(CurrentPathProperty); }
+ set { SetValue(CurrentPathProperty, value); }
+ }
+
+ private bool IsDetailPaneVisible
+ {
+ get
+ {
+ return (bool)GetValue(IsDetailPaneVisibleProperty);
+ }
+
+ set
+ {
+ SetValue(IsDetailPaneVisibleProperty, value);
+ }
+ }
+
+ private int FileUploading { get; set; }
+
+ private string ErrorMessage { get; set; }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.cs
new file mode 100644
index 00000000000..afaded690b8
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFileList/SharePointFileList.cs
@@ -0,0 +1,349 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Graph;
+using Newtonsoft.Json;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph
+{
+ ///
+ /// The SharePointFiles Control displays a simple list of SharePoint Files.
+ ///
+ [TemplatePart(Name = ControlFileList, Type = typeof(ListView))]
+ [TemplatePart(Name = ControlBack, Type = typeof(Button))]
+ [TemplatePart(Name = ControlCancel, Type = typeof(Button))]
+ [TemplatePart(Name = ControlDelete, Type = typeof(Button))]
+ [TemplatePart(Name = ControlDownload, Type = typeof(Button))]
+ [TemplatePart(Name = ControlLoadMore, Type = typeof(Button))]
+ [TemplatePart(Name = ControlShare, Type = typeof(Button))]
+ [TemplatePart(Name = ControlUpload, Type = typeof(Button))]
+ [TemplatePart(Name = ControlError, Type = typeof(HyperlinkButton))]
+ [TemplateVisualState(Name = UploadStatusNotUploading, GroupName = UploadStatus)]
+ [TemplateVisualState(Name = UploadStatusUploading, GroupName = UploadStatus)]
+ [TemplateVisualState(Name = UploadStatusError, GroupName = UploadStatus)]
+ [TemplateVisualState(Name = DetailPaneStatesHide, GroupName = DetailPaneStates)]
+ [TemplateVisualState(Name = DetailPaneStatesSide, GroupName = DetailPaneStates)]
+ [TemplateVisualState(Name = DetailPaneStatesBottom, GroupName = DetailPaneStates)]
+ [TemplateVisualState(Name = DetailPaneStatesFull, GroupName = DetailPaneStates)]
+ [TemplateVisualState(Name = NavStatesFolderReadonly, GroupName = NavStates)]
+ [TemplateVisualState(Name = NavStatesFolderEdit, GroupName = NavStates)]
+ [TemplateVisualState(Name = NavStatesFileReadonly, GroupName = NavStates)]
+ [TemplateVisualState(Name = NavStatesFileEdit, GroupName = NavStates)]
+ public partial class SharePointFileList : Control
+ {
+ ///
+ /// File is selected
+ ///
+ public event EventHandler FileSelected;
+
+ private string _driveId;
+ private string _driveName;
+ private Stack _driveItemPath = new Stack();
+ private string _pathVisualState = string.Empty;
+ private IDriveItemChildrenCollectionRequest _nextPageRequest = null;
+ private CancellationTokenSource _cancelUpload = new CancellationTokenSource();
+ private CancellationTokenSource _cancelLoadFile = new CancellationTokenSource();
+ private CancellationTokenSource _cancelGetDetails = new CancellationTokenSource();
+ private ListView _list;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SharePointFileList()
+ {
+ DefaultStyleKey = typeof(SharePointFileList);
+ }
+
+ ///
+ /// Called when applying the control template.
+ ///
+ protected override void OnApplyTemplate()
+ {
+ _list = GetTemplateChild(ControlFileList) as ListView;
+ if (_list != null)
+ {
+ _list.SelectionChanged += List_SelectionChanged;
+ _list.ItemClick += List_ItemClick;
+ }
+
+ Button back = GetTemplateChild(ControlBack) as Button;
+ if (back != null)
+ {
+ back.Click += Back_Click;
+ }
+
+ Button cancel = GetTemplateChild(ControlCancel) as Button;
+ if (cancel != null)
+ {
+ cancel.Click += Cancel_Click;
+ }
+
+ Button delete = GetTemplateChild(ControlDelete) as Button;
+ if (delete != null)
+ {
+ delete.Click += Delete_Click;
+ }
+
+ Button download = GetTemplateChild(ControlDownload) as Button;
+ if (download != null)
+ {
+ download.Click += Download_Click;
+ }
+
+ Button loadMore = GetTemplateChild(ControlLoadMore) as Button;
+ if (loadMore != null)
+ {
+ loadMore.Click += LoadMore_Click;
+ }
+
+ Button share = GetTemplateChild(ControlShare) as Button;
+ if (share != null)
+ {
+ share.Click += Share_Click;
+ }
+
+ Button upload = GetTemplateChild(ControlUpload) as Button;
+ if (upload != null)
+ {
+ upload.Click += Upload_Click;
+ }
+
+ HyperlinkButton error = GetTemplateChild(ControlError) as HyperlinkButton;
+ if (error != null)
+ {
+ error.Click += ShowErrorDetails_Click;
+ }
+
+ base.OnApplyTemplate();
+ }
+
+ ///
+ /// Retrieves an appropriate Drive URL from a SharePoint document library root URL
+ ///
+ /// Raw URL for SharePoint document library
+ /// Drive URL
+ public async Task GetDriveUrlFromSharePointUrlAsync(string rawDocLibUrl)
+ {
+ if (string.IsNullOrEmpty(rawDocLibUrl))
+ {
+ return rawDocLibUrl;
+ }
+
+ rawDocLibUrl = WebUtility.UrlDecode(rawDocLibUrl);
+
+ Match match = Regex.Match(rawDocLibUrl, @"(https?://([^/]+)((/[^/?]+)*?)(/[^/?]+))(/(Forms/\w+.aspx)?)?(\?.*)?$", RegexOptions.IgnoreCase);
+ string docLibUrl = match.Groups[1].Value;
+ string hostName = match.Groups[2].Value;
+ string siteRelativePath = match.Groups[3].Value;
+ if (string.IsNullOrEmpty(siteRelativePath))
+ {
+ siteRelativePath = "/";
+ }
+
+ if (await GraphService.TryLoginAsync())
+ {
+ GraphServiceClient graphServiceClient = GraphService.GraphProvider;
+
+ Site site = await graphServiceClient.Sites.GetByPath(siteRelativePath, hostName).Request().GetAsync();
+ ISiteDrivesCollectionPage drives = await graphServiceClient.Sites[site.Id].Drives.Request().GetAsync();
+
+ Drive drive = drives.SingleOrDefault(o => WebUtility.UrlDecode(o.WebUrl).Equals(docLibUrl, StringComparison.CurrentCultureIgnoreCase));
+ if (drive == null)
+ {
+ throw new Exception("Drive not found");
+ }
+
+ return graphServiceClient.Drives[drive.Id].RequestUrl;
+ }
+
+ return rawDocLibUrl;
+ }
+
+ private async Task InitDriveAsync(string driveUrl)
+ {
+ try
+ {
+ string realDriveURL;
+ if (driveUrl.StartsWith("https://graph.microsoft.com/", StringComparison.CurrentCultureIgnoreCase))
+ {
+ realDriveURL = driveUrl;
+ }
+ else
+ {
+ realDriveURL = await GetDriveUrlFromSharePointUrlAsync(driveUrl);
+ }
+
+ await GraphService.TryLoginAsync();
+ GraphServiceClient graphServiceClient = GraphService.GraphProvider;
+ HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, realDriveURL);
+ await graphServiceClient.AuthenticationProvider.AuthenticateRequestAsync(message);
+
+ HttpResponseMessage result = await graphServiceClient.HttpProvider.SendAsync(message);
+ if (result.StatusCode == HttpStatusCode.OK)
+ {
+ string json = await result.Content.ReadAsStringAsync();
+ Drive drive = JsonConvert.DeserializeObject(json);
+ if (drive != null)
+ {
+ _driveId = drive.Id;
+ _driveName = drive.Name;
+ _driveItemPath.Clear();
+ DriveItem rootDriveItem = await graphServiceClient.Drives[_driveId].Root.Request().GetAsync();
+ _driveItemPath.Push(rootDriveItem);
+ UpdateCurrentPath();
+ await LoadFilesAsync(rootDriveItem.Id);
+ BackButtonVisibility = Visibility.Collapsed;
+ }
+ }
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ private async Task LoadFilesAsync(string driveItemId, int pageIndex = 0)
+ {
+ IsDetailPaneVisible = false;
+ HideDetailsPane();
+ if (!string.IsNullOrEmpty(_driveId))
+ {
+ try
+ {
+ _cancelLoadFile.Cancel(false);
+ _cancelLoadFile.Dispose();
+ _cancelLoadFile = new CancellationTokenSource();
+ _list.Items.Clear();
+ VisualStateManager.GoToState(this, NavStatesFolderReadonly, false);
+ QueryOption queryOption = new QueryOption("$top", PageSize.ToString());
+
+ await GraphService.TryLoginAsync();
+ GraphServiceClient graphServiceClient = GraphService.GraphProvider;
+ Task taskFiles = graphServiceClient.Drives[_driveId].Items[driveItemId].Children.Request(new List