diff --git a/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj b/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj index 7bbf962..e9a1003 100644 --- a/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj +++ b/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj @@ -7,12 +7,10 @@ This package includes .NET Standard authentication helpers such as: - BaseProvider: - IProvider: - - MockPRovider: + - MockProvider: - ProviderManager: - - ProviderManagerChangedState: - ProviderState: - ProviderStateChangedEventArgs: - - ProviderUpdatedEventArgs: Community Toolkit Provider Authentication Auth diff --git a/CommunityToolkit.Authentication/ProviderManager.cs b/CommunityToolkit.Authentication/ProviderManager.cs index 5bae3ea..b4f82ef 100644 --- a/CommunityToolkit.Authentication/ProviderManager.cs +++ b/CommunityToolkit.Authentication/ProviderManager.cs @@ -15,7 +15,7 @@ namespace CommunityToolkit.Authentication /// ProviderManager.Instance.GlobalProvider = await MsalProvider.CreateAsync(...); /// /// - public partial class ProviderManager : INotifyPropertyChanged + public partial class ProviderManager { /// /// Gets the name of the toolkit client to identify self in Graph calls. @@ -28,12 +28,14 @@ public partial class ProviderManager : INotifyPropertyChanged public static ProviderManager Instance { get; } = new ProviderManager(); /// - /// Event called when the changes. + /// Event called when the instance changes. /// - public event EventHandler ProviderUpdated; + public event EventHandler ProviderUpdated; - /// - public event PropertyChangedEventHandler PropertyChanged; + /// + /// Event called when the state changes. + /// + public event EventHandler ProviderStateChanged; private IProvider _provider; @@ -49,6 +51,7 @@ public IProvider GlobalProvider set { + var oldState = _provider?.State; if (_provider != null) { _provider.StateChanged -= ProviderStateChanged; @@ -56,14 +59,14 @@ public IProvider GlobalProvider _provider = value; + var newState = _provider?.State; if (_provider != null) { _provider.StateChanged += ProviderStateChanged; } - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GlobalProvider))); - ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderChanged)); - ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderStateChanged)); + ProviderUpdated?.Invoke(this, _provider); + ProviderStateChanged?.Invoke(this, new ProviderStateChangedEventArgs(oldState, newState)); } } @@ -71,10 +74,5 @@ private ProviderManager() { // Use Instance } - - private void ProviderStateChanged(object sender, ProviderStateChangedEventArgs e) - { - ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderStateChanged)); - } } } diff --git a/CommunityToolkit.Authentication/ProviderManagerChangedState.cs b/CommunityToolkit.Authentication/ProviderManagerChangedState.cs deleted file mode 100644 index 038df46..0000000 --- a/CommunityToolkit.Authentication/ProviderManagerChangedState.cs +++ /dev/null @@ -1,22 +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. - -namespace CommunityToolkit.Authentication -{ - /// - /// 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/CommunityToolkit.Authentication/ProviderStateChangedEventArgs.cs b/CommunityToolkit.Authentication/ProviderStateChangedEventArgs.cs index f897305..0603613 100644 --- a/CommunityToolkit.Authentication/ProviderStateChangedEventArgs.cs +++ b/CommunityToolkit.Authentication/ProviderStateChangedEventArgs.cs @@ -16,7 +16,7 @@ public class ProviderStateChangedEventArgs : EventArgs /// /// Previous . /// Current . - public ProviderStateChangedEventArgs(ProviderState oldState, ProviderState newState) + public ProviderStateChangedEventArgs(ProviderState? oldState, ProviderState? newState) { OldState = oldState; NewState = newState; @@ -25,11 +25,11 @@ public ProviderStateChangedEventArgs(ProviderState oldState, ProviderState newSt /// /// Gets the previous state of the . /// - public ProviderState OldState { get; private set; } + public ProviderState? OldState { get; private set; } /// /// Gets the new state of the . /// - public ProviderState NewState { get; private set; } + public ProviderState? NewState { get; private set; } } } \ No newline at end of file diff --git a/CommunityToolkit.Authentication/ProviderUpdatedEventArgs.cs b/CommunityToolkit.Authentication/ProviderUpdatedEventArgs.cs deleted file mode 100644 index 9e8375a..0000000 --- a/CommunityToolkit.Authentication/ProviderUpdatedEventArgs.cs +++ /dev/null @@ -1,28 +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 System; - -namespace CommunityToolkit.Authentication -{ - /// - /// 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/CommunityToolkit.Graph.Uwp/Controls/LoginButton/LoginButton.cs b/CommunityToolkit.Graph.Uwp/Controls/LoginButton/LoginButton.cs new file mode 100644 index 0000000..05b01c8 --- /dev/null +++ b/CommunityToolkit.Graph.Uwp/Controls/LoginButton/LoginButton.cs @@ -0,0 +1,229 @@ +// 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.Diagnostics; +using System.Threading.Tasks; +using CommunityToolkit.Authentication; +using CommunityToolkit.Graph.Extensions; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; + +namespace CommunityToolkit.Graph.Uwp.Controls +{ + /// + /// The control is a button which can be used to sign the user in or show them profile details. + /// + [TemplatePart(Name = LoginButtonPart, Type = typeof(Button))] + [TemplatePart(Name = SignOutButtonPart, Type = typeof(ButtonBase))] + public partial class LoginButton : Control + { + private const string LoginButtonPart = "PART_LoginButton"; + private const string SignOutButtonPart = "PART_SignOutButton"; + + private Button _loginButton; + private ButtonBase _signOutButton; + + /// + /// Initializes a new instance of the class. + /// + public LoginButton() + { + this.DefaultStyleKey = typeof(LoginButton); + + ProviderManager.Instance.ProviderStateChanged += (sender, args) => LoadData(); + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + IsLoading = true; + LoadData(); + + if (_loginButton != null) + { + _loginButton.Click -= LoginButton_Click; + } + + _loginButton = GetTemplateChild(LoginButtonPart) as Button; + + if (_loginButton != null) + { + _loginButton.Click += LoginButton_Click; + } + + if (_signOutButton != null) + { + _signOutButton.Click -= LoginButton_Click; + } + + _signOutButton = GetTemplateChild(SignOutButtonPart) as ButtonBase; + + if (_signOutButton != null) + { + _signOutButton.Click += SignOutButton_Click; + } + } + + private async void LoginButton_Click(object sender, RoutedEventArgs e) + { + if (this.UserDetails != null) + { + if (FlyoutBase.GetAttachedFlyout(_loginButton) is FlyoutBase flyout) + { + flyout.ShowAt(_loginButton); + } + } + else + { + var cargs = new CancelEventArgs(); + LoginInitiated?.Invoke(this, cargs); + + if (!cargs.Cancel) + { + await SignInAsync(); + } + } + } + + private async void SignOutButton_Click(object sender, RoutedEventArgs e) + { + await SignOutAsync(); + } + + private async void LoadData() + { + var provider = ProviderManager.Instance.GlobalProvider; + + if (provider == null) + { + return; + } + + if (provider.State == ProviderState.Loading) + { + IsLoading = true; + } + else if (provider.State == ProviderState.SignedIn) + { + try + { + // https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/master/src/components/mgt-login/mgt-login.ts#L139 + // TODO: Batch with photo request later? https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/issues/29 + UserDetails = await provider.GetClient().GetMeAsync(); + } + catch (Exception e) + { + LoginFailed?.Invoke(this, new LoginFailedEventArgs(e)); + } + + IsLoading = false; + } + else if (provider.State == ProviderState.SignedOut) + { + UserDetails = null; // What if this was user provided? Should we not hook into these events then? + + IsLoading = false; + } + else + { + // Provider in Loading state + Debug.Fail("unsupported state"); + } + } + + /// + /// Initiates logging in with the current registered in the . + /// + /// A representing the asynchronous operation. + public async Task SignInAsync() + { + if (UserDetails != null || IsLoading) + { + return; + } + + var provider = ProviderManager.Instance.GlobalProvider; + + if (provider != null) + { + try + { + IsLoading = true; + await provider.SignInAsync(); + + if (provider.State == ProviderState.SignedIn) + { + // TODO: include user details? + LoginCompleted?.Invoke(this, new EventArgs()); + + LoadData(); + } + else + { + LoginFailed?.Invoke(this, new LoginFailedEventArgs(new TimeoutException("Login did not complete."))); + } + } + catch (Exception e) + { + LoginFailed?.Invoke(this, new LoginFailedEventArgs(e)); + } + finally + { + IsLoading = false; + } + } + } + + /// + /// Log a signed-in user out. + /// + /// A representing the asynchronous operation. + public async Task SignOutAsync() + { + // Close Menu + if (FlyoutBase.GetAttachedFlyout(_loginButton) is FlyoutBase flyout) + { + flyout.Hide(); + } + + if (IsLoading) + { + return; + } + + var cargs = new CancelEventArgs(); + LogoutInitiated?.Invoke(this, cargs); + + if (cargs.Cancel) + { + return; + } + + if (UserDetails != null) + { + UserDetails = null; + } + else + { + return; // No-op + } + + var provider = ProviderManager.Instance.GlobalProvider; + + if (provider != null) + { + IsLoading = true; + await provider.SignOutAsync(); + IsLoading = false; + + LogoutCompleted?.Invoke(this, new EventArgs()); + } + } + } +} diff --git a/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs b/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs new file mode 100644 index 0000000..24ec786 --- /dev/null +++ b/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs @@ -0,0 +1,228 @@ +// 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 CommunityToolkit.Authentication; +using CommunityToolkit.Graph.Extensions; +using Microsoft.Graph; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Imaging; + +namespace CommunityToolkit.Graph.Uwp.Controls +{ + /// + /// The control displays a user photo and can display their name and e-mail. + /// + public partial class PersonView : Control + { + private const string PersonViewDefaultImageSourceResourceName = "PersonViewDefaultImageSource"; + + /// + /// value used to retrieve the signed-in user's info. + /// + public const string PersonQueryMe = "me"; + + private string _photoId = null; + + private string _defaultImageSource = "ms-appx:///Microsoft.Toolkit.Graph.Controls/Assets/person.png"; + + private BitmapImage _defaultImage; + + 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?.UriSource?.AbsoluteUri == pv._defaultImageSource || pv?.PersonDetails?.Id != pv._photoId) + { + // Reload Image + pv.UserPhoto = pv._defaultImage; + await pv.LoadImageAsync(pv.PersonDetails); + } + else if (pv?.PersonDetails?.Id != pv._photoId) + { + pv.UserPhoto = pv._defaultImage; + pv._photoId = null; + } + } + } + } + + private static void QueryPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is PersonView view) + { + view.PersonDetails = null; + view.LoadData(); + } + } + + private static void PersonViewTypePropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is PersonView pv) + { + pv.IsLargeImage = pv.PersonViewType == PersonViewType.Avatar; + } + } + + /// + /// Initializes a new instance of the class. + /// + public PersonView() + { + this.DefaultStyleKey = typeof(PersonView); + + _defaultImage = new BitmapImage(new Uri(_defaultImageSource)); + + ProviderManager.Instance.ProviderStateChanged += (sender, args) => LoadData(); + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (Resources.TryGetValue(PersonViewDefaultImageSourceResourceName, out object value) && value is string uri) + { + _defaultImageSource = uri; + _defaultImage = new BitmapImage(new Uri(_defaultImageSource)); // TODO: Couldn't load image from app package, only remote or in our assembly? + UserPhoto = _defaultImage; + } + + LoadData(); + } + + private async void LoadData() + { + var provider = ProviderManager.Instance.GlobalProvider; + + if (provider == null || provider.State != ProviderState.SignedIn) + { + // Set back to Default if not signed-in + if (provider != null) + { + UserPhoto = _defaultImage; + } + + 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.GetClient().GetUserAsync(UserId); + } + catch + { + } + + try + { + // TODO: Move to LoadImage based on previous call? + await DecodeStreamAsync(await provider.GetBetaClient().GetUserPhoto(UserId)); + _photoId = UserId; + } + catch + { + } + } + else + { + try + { + user = await provider.GetClient().GetMeAsync(); + } + catch + { + } + + try + { + await DecodeStreamAsync(await provider.GetBetaClient().GetMyPhotoAsync()); + _photoId = user.Id; + } + catch + { + } + } + + if (user != null) + { + PersonDetails = user.ToPerson(); + } + } + else if (PersonDetails == null && !string.IsNullOrWhiteSpace(PersonQuery)) + { + var people = await provider.GetClient().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.GetBetaClient(); + + 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/CommunityToolkit.Graph.Uwp/Triggers/ProviderStateTrigger.cs b/CommunityToolkit.Graph.Uwp/Triggers/ProviderStateTrigger.cs new file mode 100644 index 0000000..57c9368 --- /dev/null +++ b/CommunityToolkit.Graph.Uwp/Triggers/ProviderStateTrigger.cs @@ -0,0 +1,77 @@ +// 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 CommunityToolkit.Authentication; +using Microsoft.Toolkit.Uwp; +using Microsoft.Toolkit.Uwp.Helpers; +using Windows.System; +using Windows.UI.Xaml; + +namespace CommunityToolkit.Graph.Uwp +{ + /// + /// A StateTrigger for detecting when the global authentication provider has been signed in. + /// + public class ProviderStateTrigger : StateTriggerBase + { + /// + /// Identifies the DependencyProperty. + /// + public static readonly DependencyProperty StateProperty = + DependencyProperty.Register(nameof(State), typeof(ProviderState), typeof(ProviderStateTrigger), new PropertyMetadata(null, OnStateChanged)); + + private static void OnStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ProviderStateTrigger instance) + { + instance.UpdateState(); + } + } + + private readonly DispatcherQueue _dispatcherQueue; + + /// + /// Gets or sets the expected ProviderState. + /// + public ProviderState? State + { + get => (ProviderState?)GetValue(StateProperty); + set => SetValue(StateProperty, value); + } + + /// + /// Initializes a new instance of the class. + /// + public ProviderStateTrigger() + { + _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + var weakEvent = + new WeakEventListener(this) + { + OnEventAction = (instance, source, args) => OnProviderStateChanged(source, args), + OnDetachAction = (weakEventListener) => ProviderManager.Instance.ProviderStateChanged -= weakEventListener.OnEvent, + }; + ProviderManager.Instance.ProviderStateChanged += weakEvent.OnEvent; + UpdateState(); + } + + private void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e) + { + _ = _dispatcherQueue.EnqueueAsync(UpdateState, DispatcherQueuePriority.Normal); + } + + private void UpdateState() + { + var provider = ProviderManager.Instance.GlobalProvider; + if (State != null && provider?.State != null) + { + SetActive(provider?.State == State); + } + else + { + SetActive(false); + } + } + } +} \ No newline at end of file diff --git a/CommunityToolkit.Graph/Extensions/ProviderExtensions.cs b/CommunityToolkit.Graph/Extensions/ProviderExtensions.cs index ee50139..e4ef1da 100644 --- a/CommunityToolkit.Graph/Extensions/ProviderExtensions.cs +++ b/CommunityToolkit.Graph/Extensions/ProviderExtensions.cs @@ -12,60 +12,30 @@ namespace CommunityToolkit.Graph.Extensions /// public static class ProviderExtensions { - private static GraphServiceClient _client; - private static GraphServiceClient _betaClient; - - static ProviderExtensions() - { - ProviderManager.Instance.ProviderUpdated += OnProviderUpdated; - } - - private static void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e) - { - var providerManager = sender as ProviderManager; - if (e.Reason == ProviderManagerChangedState.ProviderChanged || !(providerManager.GlobalProvider?.State == ProviderState.SignedIn)) - { - _client = null; - _betaClient = null; - } - } - /// - /// Lazily gets a GraphServiceClient instance based on the current GlobalProvider. - /// The client instance is cleared whenever the GlobalProvider changes. + /// Gets a GraphServiceClient instance based on the current GlobalProvider. /// /// The provider for authenticating Graph calls. /// A GraphServiceClient instance. public static GraphServiceClient GetClient(this IProvider provider) { - if (_client == null && provider?.State == ProviderState.SignedIn) + return new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) => { - _client = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) => - { - await provider.AuthenticateRequestAsync(requestMessage); - })); - } - - return _client; + await provider.AuthenticateRequestAsync(requestMessage); + })); } /// - /// Lazily gets a GraphServiceClient instance based on the current GlobalProvider, but configured for the beta endpoint. - /// The beta client instance is cleared whenever the GlobalProvider changes. + /// Gets a GraphServiceClient instance based on the current GlobalProvider, but configured for the beta endpoint. /// /// The provider for authenticating Graph calls. /// A GraphServiceClient instance configured for the beta endpoint. public static GraphServiceClient GetBetaClient(this IProvider provider) { - if (_betaClient == null && provider?.State == ProviderState.SignedIn) + return new GraphServiceClient("https://graph.microsoft.com/beta", new DelegateAuthenticationProvider(async (requestMessage) => { - _betaClient = new GraphServiceClient("https://graph.microsoft.com/beta", new DelegateAuthenticationProvider(async (requestMessage) => - { - await provider.AuthenticateRequestAsync(requestMessage); - })); - } - - return _betaClient; + await provider.AuthenticateRequestAsync(requestMessage); + })); } } } diff --git a/README.md b/README.md index f0f9634..b67824e 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ private async Task> GetDefaultTaskListAsync() **That's all you need to get started!** -You can use the `ProviderManager.Instance` to listen to changes in authentication status with the `ProviderUpdated` event or get direct access to the [.NET Graph Beta API](https://github.com/microsoftgraph/msgraph-beta-sdk-dotnet) through `ProviderManager.Instance.GlobalProvider.GetBetaClient()`, just be sure to check if the `GlobalProvider` has been set first and its `State` is `SignedIn`: +You can use the `ProviderManager.Instance` to listen to changes in authentication status with the `ProviderStateChanged` event or get direct access to the [.NET Graph Beta API](https://github.com/microsoftgraph/msgraph-beta-sdk-dotnet) through `ProviderManager.Instance.GlobalProvider.GetBetaClient()`, just be sure to check if the `GlobalProvider` has been set first and its `State` is `SignedIn`: ```csharp using CommunityToolkit.Authentication; diff --git a/SampleTest/MainPage.xaml.cs b/SampleTest/MainPage.xaml.cs new file mode 100644 index 0000000..4a4c577 --- /dev/null +++ b/SampleTest/MainPage.xaml.cs @@ -0,0 +1,95 @@ +// 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 CommunityToolkit.Authentication; +using CommunityToolkit.Graph.Extensions; +using Microsoft.Graph; +using Microsoft.Graph.Extensions; +using System; +using System.Text.RegularExpressions; +using Windows.UI.Xaml.Controls; + +namespace SampleTest +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class MainPage : Page + { + // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2407 + public DateTime Today => DateTimeOffset.Now.Date.ToUniversalTime(); + + // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2407 + public DateTime ThreeDaysFromNow => Today.AddDays(3); + + public IBaseRequestBuilder CalendarViewBuilder; + public IBaseRequestBuilder MessagesBuilder; + public IBaseRequestBuilder PlannerTasksBuilder; + public IBaseRequestBuilder TeamsChannelMessagesBuilder; + + public MainPage() + { + this.InitializeComponent(); + + ProviderManager.Instance.ProviderUpdated += this.OnProviderUpdated; + ProviderManager.Instance.ProviderStateChanged += this.OnProviderStateChanged; + } + + private void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e) + { + if (e.NewState == ProviderState.SignedIn) + { + var graphClient = ProviderManager.Instance.GlobalProvider.GetClient(); + + CalendarViewBuilder = graphClient.Me.CalendarView; + MessagesBuilder = graphClient.Me.Messages; + PlannerTasksBuilder = graphClient.Me.Planner.Tasks; + TeamsChannelMessagesBuilder = graphClient.Teams["02bd9fd6-8f93-4758-87c3-1fb73740a315"].Channels["19:d0bba23c2fc8413991125a43a54cc30e@thread.skype"].Messages; + } + else + { + ClearRequestBuilders(); + } + } + + private void OnProviderUpdated(object sender, IProvider provider) + { + if (provider == null) + { + ClearRequestBuilders(); + } + } + + private void ClearRequestBuilders() + { + CalendarViewBuilder = null; + MessagesBuilder = null; + PlannerTasksBuilder = null; + TeamsChannelMessagesBuilder = null; + } + + public static string ToLocalTime(DateTimeTimeZone value) + { + // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2407 + return value.ToDateTimeOffset().LocalDateTime.ToString("g"); + } + + public static string ToLocalTime(DateTimeOffset? value) + { + // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2654 + return value?.LocalDateTime.ToString("g"); + } + + public static string RemoveWhitespace(string value) + { + // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2654 + return Regex.Replace(value, @"\t|\r|\n", " "); + } + + public static bool IsTaskCompleted(int? percentCompleted) + { + return percentCompleted == 100; + } + } +} diff --git a/SampleTest/Samples/RoamingSettings/RoamingSettingsViewModel.cs b/SampleTest/Samples/RoamingSettings/RoamingSettingsViewModel.cs new file mode 100644 index 0000000..011c6b1 --- /dev/null +++ b/SampleTest/Samples/RoamingSettings/RoamingSettingsViewModel.cs @@ -0,0 +1,195 @@ +// 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 CommunityToolkit.Authentication; +using CommunityToolkit.Graph.Uwp.Helpers.RoamingSettings; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace SampleTest.Samples +{ + public class RoamingSettingsViewModel : INotifyPropertyChanged + { + private IProvider GlobalProvider => ProviderManager.Instance.GlobalProvider; + + public event PropertyChangedEventHandler PropertyChanged; + + private string _errorText; + public string ErrorText + { + get => _errorText; + set => Set(ref _errorText, value); + } + + private RoamingSettingsHelper _roamingSettings; + + private ObservableCollection> _additionalData; + public ObservableCollection> AdditionalData + { + get => _additionalData; + set => Set(ref _additionalData, value); + } + + private string _keyInputText; + public string KeyInputText + { + get => _keyInputText; + set => Set(ref _keyInputText, value); + } + + private string _valueInputText; + public string ValueInputText + { + get => _valueInputText; + set => Set(ref _valueInputText, value); + } + + public RoamingSettingsViewModel() + { + _roamingSettings = null; + _keyInputText = string.Empty; + _valueInputText = string.Empty; + + ProviderManager.Instance.ProviderStateChanged += (s, e) => CheckState(); + CheckState(); + } + + public void GetValue() + { + try + { + ErrorText = string.Empty; + ValueInputText = string.Empty; + + string key = KeyInputText; + string value = _roamingSettings.Read(key); + + ValueInputText = value; + } + catch (Exception e) + { + ErrorText = e.Message; + } + } + + public void SetValue() + { + try + { + ErrorText = string.Empty; + + _roamingSettings.Save(KeyInputText, ValueInputText); + + SyncRoamingSettings(); + } + catch (Exception e) + { + ErrorText = e.Message; + } + } + + public async void CreateCustomRoamingSettings() + { + try + { + ErrorText = string.Empty; + + await _roamingSettings.Create(); + + AdditionalData = new ObservableCollection>(_roamingSettings.Cache); + + KeyInputText = string.Empty; + ValueInputText = string.Empty; + } + catch (Exception e) + { + ErrorText = e.Message; + } + } + + public async void DeleteCustomRoamingSettings() + { + try + { + ErrorText = string.Empty; + + await _roamingSettings.Delete(); + + AdditionalData?.Clear(); + KeyInputText = string.Empty; + ValueInputText = string.Empty; + } + catch (Exception e) + { + ErrorText = e.Message; + } + } + + public async void SyncRoamingSettings() + { + try + { + ErrorText = string.Empty; + AdditionalData?.Clear(); + + await _roamingSettings.Sync(); + if (_roamingSettings.Cache != null) + { + AdditionalData = new ObservableCollection>(_roamingSettings.Cache); + } + } + catch (Exception e) + { + ErrorText = e.Message; + } + } + + private async void CheckState() + { + if (GlobalProvider != null && GlobalProvider.State == ProviderState.SignedIn) + { + await LoadState(); + } + else + { + ClearState(); + } + } + + private async Task LoadState() + { + try + { + ClearState(); + + _roamingSettings = await RoamingSettingsHelper.CreateForCurrentUser(); + } + catch (Exception e) + { + ErrorText = e.Message; + } + } + + private void ClearState() + { + _roamingSettings = null; + + KeyInputText = string.Empty; + ValueInputText = string.Empty; + } + + private void Set(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (!EqualityComparer.Default.Equals(field, value)) + { + field = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/Samples/ManualGraphRequestSample/LoginButton.xaml.cs b/Samples/ManualGraphRequestSample/LoginButton.xaml.cs index 26aba11..43e0853 100644 --- a/Samples/ManualGraphRequestSample/LoginButton.xaml.cs +++ b/Samples/ManualGraphRequestSample/LoginButton.xaml.cs @@ -17,7 +17,7 @@ public LoginButton() { InitializeComponent(); - ProviderManager.Instance.ProviderUpdated += (s, e) => UpdateState(); + ProviderManager.Instance.ProviderStateChanged += (s, e) => UpdateState(); UpdateState(); } diff --git a/Samples/ManualGraphRequestSample/MainPage.xaml.cs b/Samples/ManualGraphRequestSample/MainPage.xaml.cs index 98f7daa..cb6fc20 100644 --- a/Samples/ManualGraphRequestSample/MainPage.xaml.cs +++ b/Samples/ManualGraphRequestSample/MainPage.xaml.cs @@ -21,15 +21,13 @@ public MainPage() { InitializeComponent(); - ProviderManager.Instance.ProviderUpdated += OnProviderUpdated; + ProviderManager.Instance.ProviderStateChanged += OnProviderStateChanged; ProviderManager.Instance.GlobalProvider = new WindowsProvider(new string[] { "User.Read", "Tasks.ReadWrite" }); } - private async void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e) + private async void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e) { - IProvider provider = ProviderManager.Instance.GlobalProvider; - - switch (provider?.State) + switch (e.NewState) { case ProviderState.SignedOut: TaskCollection.Clear(); diff --git a/Samples/UwpMsalProviderSample/LoginButton.xaml.cs b/Samples/UwpMsalProviderSample/LoginButton.xaml.cs index 4e0cbb6..24c3ecf 100644 --- a/Samples/UwpMsalProviderSample/LoginButton.xaml.cs +++ b/Samples/UwpMsalProviderSample/LoginButton.xaml.cs @@ -17,7 +17,7 @@ public LoginButton() { InitializeComponent(); - ProviderManager.Instance.ProviderUpdated += (s, e) => UpdateState(); + ProviderManager.Instance.ProviderStateChanged += (s, e) => UpdateState(); UpdateState(); } diff --git a/Samples/UwpMsalProviderSample/MainPage.xaml.cs b/Samples/UwpMsalProviderSample/MainPage.xaml.cs index c43e0fc..4e59f4c 100644 --- a/Samples/UwpMsalProviderSample/MainPage.xaml.cs +++ b/Samples/UwpMsalProviderSample/MainPage.xaml.cs @@ -14,25 +14,24 @@ public MainPage() { InitializeComponent(); - ProviderManager.Instance.ProviderUpdated += OnProviderUpdated; + ProviderManager.Instance.ProviderStateChanged += OnProviderStateChanged; } - private async void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e) + private async void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e) { - var provider = ProviderManager.Instance.GlobalProvider; - if (provider == null || provider.State != ProviderState.SignedIn) - { - SignedInUserTextBlock.Text = "Please sign in."; - } - else + if (e.NewState == ProviderState.SignedIn) { SignedInUserTextBlock.Text = "Signed in as..."; - var graphClient = provider.GetClient(); + var graphClient = ProviderManager.Instance.GlobalProvider.GetClient(); var me = await graphClient.Me.Request().GetAsync(); SignedInUserTextBlock.Text = "Signed in as: " + me.DisplayName; } + else + { + SignedInUserTextBlock.Text = "Please sign in."; + } } } } diff --git a/Samples/UwpWindowsProviderSample/LoginButton.xaml.cs b/Samples/UwpWindowsProviderSample/LoginButton.xaml.cs index 2b0c74d..cd8008c 100644 --- a/Samples/UwpWindowsProviderSample/LoginButton.xaml.cs +++ b/Samples/UwpWindowsProviderSample/LoginButton.xaml.cs @@ -17,7 +17,7 @@ public LoginButton() { InitializeComponent(); - ProviderManager.Instance.ProviderUpdated += (s, e) => UpdateState(); + ProviderManager.Instance.ProviderStateChanged += (s, e) => UpdateState(); UpdateState(); } diff --git a/Samples/UwpWindowsProviderSample/MainPage.xaml.cs b/Samples/UwpWindowsProviderSample/MainPage.xaml.cs index 3bb5de0..155e306 100644 --- a/Samples/UwpWindowsProviderSample/MainPage.xaml.cs +++ b/Samples/UwpWindowsProviderSample/MainPage.xaml.cs @@ -14,27 +14,26 @@ public MainPage() { InitializeComponent(); - ProviderManager.Instance.ProviderUpdated += OnProviderUpdated; + ProviderManager.Instance.ProviderStateChanged += OnProviderStateChanged; } - private async void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e) + private async void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e) { - var provider = ProviderManager.Instance.GlobalProvider; - if (provider == null || provider.State != ProviderState.SignedIn) - { - SignedInUserTextBlock.Text = "Please sign in."; - ManagerButton.IsEnabled = false; - } - else + if (e.NewState == ProviderState.SignedIn) { ManagerButton.IsEnabled = true; SignedInUserTextBlock.Text = "Signed in as..."; - var graphClient = provider.GetClient(); + var graphClient = ProviderManager.Instance.GlobalProvider.GetClient(); var me = await graphClient.Me.Request().GetAsync(); SignedInUserTextBlock.Text = "Signed in as: " + me.DisplayName; } + else + { + SignedInUserTextBlock.Text = "Please sign in."; + ManagerButton.IsEnabled = false; + } } } } diff --git a/Samples/WpfMsalProviderSample/LoginButton.xaml.cs b/Samples/WpfMsalProviderSample/LoginButton.xaml.cs index 1ed9697..ee4836d 100644 --- a/Samples/WpfMsalProviderSample/LoginButton.xaml.cs +++ b/Samples/WpfMsalProviderSample/LoginButton.xaml.cs @@ -18,7 +18,7 @@ public LoginButton() { InitializeComponent(); - ProviderManager.Instance.ProviderUpdated += (s, e) => UpdateState(); + ProviderManager.Instance.ProviderStateChanged += (s, e) => UpdateState(); UpdateState(); } diff --git a/Samples/WpfMsalProviderSample/MainWindow.xaml.cs b/Samples/WpfMsalProviderSample/MainWindow.xaml.cs index 7db62ba..14635d6 100644 --- a/Samples/WpfMsalProviderSample/MainWindow.xaml.cs +++ b/Samples/WpfMsalProviderSample/MainWindow.xaml.cs @@ -17,25 +17,24 @@ public MainWindow() { InitializeComponent(); - ProviderManager.Instance.ProviderUpdated += this.OnProviderUpdated; + ProviderManager.Instance.ProviderStateChanged += OnProviderStateChanged; } - private async void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e) + private async void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e) { - var provider = ProviderManager.Instance.GlobalProvider; - if (provider == null || provider.State != ProviderState.SignedIn) - { - SignedInUserTextBlock.Text = "Please sign in."; - } - else + if (e.NewState == ProviderState.SignedIn) { SignedInUserTextBlock.Text = "Signed in as..."; - var graphClient = provider.GetClient(); + var graphClient = ProviderManager.Instance.GlobalProvider.GetClient(); var me = await graphClient.Me.Request().GetAsync(); SignedInUserTextBlock.Text = "Signed in as: " + me.DisplayName; } + else + { + SignedInUserTextBlock.Text = "Please sign in."; + } } } }