diff --git a/SpotifyAPI.Web.Auth/EmbedIOAuthServer.cs b/SpotifyAPI.Web.Auth/EmbedIOAuthServer.cs index 542784c5..b5a625a5 100644 --- a/SpotifyAPI.Web.Auth/EmbedIOAuthServer.cs +++ b/SpotifyAPI.Web.Auth/EmbedIOAuthServer.cs @@ -80,30 +80,6 @@ namespace SpotifyAPI.Web.Auth return Task.CompletedTask; } - public Uri BuildLoginUri(LoginRequest request) - { - Ensure.ArgumentNotNull(request, nameof(request)); - - StringBuilder builder = new StringBuilder(SpotifyUrls.Authorize.ToString()); - builder.Append($"?client_id={request.ClientId}"); - builder.Append($"&response_type={request.ResponseTypeParam.ToString().ToLower()}"); - builder.Append($"&redirect_uri={HttpUtility.UrlEncode(BaseUri.ToString())}"); - if (!string.IsNullOrEmpty(request.State)) - { - builder.Append($"&state={HttpUtility.UrlEncode(request.State)}"); - } - if (request.Scope != null) - { - builder.Append($"&scope={HttpUtility.UrlEncode(string.Join(" ", request.Scope))}"); - } - if (request.ShowDialog != null) - { - builder.Append($"&show_dialog={request.ShowDialog.Value}"); - } - - return new Uri(builder.ToString()); - } - public void Dispose() { Dispose(true); diff --git a/SpotifyAPI.Web.Auth/IAuthServer.cs b/SpotifyAPI.Web.Auth/IAuthServer.cs index 311cb5cb..6c5a54a8 100644 --- a/SpotifyAPI.Web.Auth/IAuthServer.cs +++ b/SpotifyAPI.Web.Auth/IAuthServer.cs @@ -12,8 +12,6 @@ namespace SpotifyAPI.Web.Auth Task Start(); Task Stop(); - Uri BuildLoginUri(LoginRequest request); - Uri BaseUri { get; } } } diff --git a/SpotifyAPI.Web.Auth/Models/Request/LoginRequest.cs b/SpotifyAPI.Web.Auth/Models/Request/LoginRequest.cs deleted file mode 100644 index 6ded9f08..00000000 --- a/SpotifyAPI.Web.Auth/Models/Request/LoginRequest.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -namespace SpotifyAPI.Web.Auth -{ - public class LoginRequest - { - public LoginRequest(string clientId, ResponseType responseType) - { - Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId)); - - ClientId = clientId; - ResponseTypeParam = responseType; - } - - public ResponseType ResponseTypeParam { get; } - public string ClientId { get; } - public string State { get; set; } - public ICollection Scope { get; set; } - public bool? ShowDialog { get; set; } - - public enum ResponseType - { - Code, - Token - } - } -} diff --git a/SpotifyAPI.Web.Examples/Example.CLI.CustomHTML/Program.cs b/SpotifyAPI.Web.Examples/Example.CLI.CustomHTML/Program.cs index 445eb6b1..f6e7e6b8 100644 --- a/SpotifyAPI.Web.Examples/Example.CLI.CustomHTML/Program.cs +++ b/SpotifyAPI.Web.Examples/Example.CLI.CustomHTML/Program.cs @@ -26,19 +26,19 @@ namespace Example.CLI.CustomHTML _server.AuthorizationCodeReceived += OnAuthorizationCodeReceived; - var request = new LoginRequest(clientId, LoginRequest.ResponseType.Code) + var request = new LoginRequest(_server.BaseUri, clientId, LoginRequest.ResponseType.Code) { Scope = new List { UserReadEmail } }; - Uri url = _server.BuildLoginUri(request); + Uri uri = request.ToUri(); try { - BrowserUtil.Open(url); + BrowserUtil.Open(uri); } catch (Exception) { - Console.WriteLine("Unable to open URL, manually open: {0}", url); + Console.WriteLine("Unable to open URL, manually open: {0}", uri); } Console.ReadKey(); diff --git a/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig/Program.cs b/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig/Program.cs index 136cef0c..fec230c6 100644 --- a/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig/Program.cs +++ b/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig/Program.cs @@ -65,19 +65,19 @@ namespace Example.CLI.PersistentConfig await _server.Start(); _server.AuthorizationCodeReceived += OnAuthorizationCodeReceived; - var request = new LoginRequest(clientId, LoginRequest.ResponseType.Code) + var request = new LoginRequest(_server.BaseUri, clientId, LoginRequest.ResponseType.Code) { Scope = new List { UserReadEmail, UserReadPrivate, PlaylistReadPrivate } }; - Uri url = _server.BuildLoginUri(request); + Uri uri = request.ToUri(); try { - BrowserUtil.Open(url); + BrowserUtil.Open(uri); } catch (Exception) { - Console.WriteLine("Unable to open URL, manually open: {0}", url); + Console.WriteLine("Unable to open URL, manually open: {0}", uri); } } diff --git a/SpotifyAPI.Web.Examples/Example.UWP/App.xaml b/SpotifyAPI.Web.Examples/Example.UWP/App.xaml index 3cb91fc1..390c068b 100644 --- a/SpotifyAPI.Web.Examples/Example.UWP/App.xaml +++ b/SpotifyAPI.Web.Examples/Example.UWP/App.xaml @@ -1,7 +1,9 @@ - - - + + + Hello World! + + diff --git a/SpotifyAPI.Web.Examples/Example.UWP/App.xaml.cs b/SpotifyAPI.Web.Examples/Example.UWP/App.xaml.cs index 0b2017be..be655f9c 100644 --- a/SpotifyAPI.Web.Examples/Example.UWP/App.xaml.cs +++ b/SpotifyAPI.Web.Examples/Example.UWP/App.xaml.cs @@ -1,100 +1,42 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; +using MvvmCross; +using MvvmCross.Platforms.Uap.Core; +using MvvmCross.Platforms.Uap.Views; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; -using Windows.Foundation; -using Windows.Foundation.Collections; +using Windows.UI.Popups; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; namespace Example.UWP { + public abstract class ExampleApp : MvxApplication, CoreApp> + { + } + + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public sealed partial class App + { /// - /// Provides application-specific behavior to supplement the default Application class. + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). /// - sealed partial class App : Application + public App() { - /// - /// Initializes the singleton application object. This is the first line of authored code - /// executed, and as such is the logical equivalent of main() or WinMain(). - /// - public App() - { - this.InitializeComponent(); - this.Suspending += OnSuspending; - } - - /// - /// Invoked when the application is launched normally by the end user. Other entry points - /// will be used such as when the application is launched to open a specific file. - /// - /// Details about the launch request and process. - protected override void OnLaunched(LaunchActivatedEventArgs e) - { - Frame rootFrame = Window.Current.Content as Frame; - - // Do not repeat app initialization when the Window already has content, - // just ensure that the window is active - if (rootFrame == null) - { - // Create a Frame to act as the navigation context and navigate to the first page - rootFrame = new Frame(); - - rootFrame.NavigationFailed += OnNavigationFailed; - - if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) - { - //TODO: Load state from previously suspended application - } - - // Place the frame in the current Window - Window.Current.Content = rootFrame; - } - - if (e.PrelaunchActivated == false) - { - if (rootFrame.Content == null) - { - // When the navigation stack isn't restored navigate to the first page, - // configuring the new page by passing required information as a navigation - // parameter - rootFrame.Navigate(typeof(MainPage), e.Arguments); - } - // Ensure the current window is active - Window.Current.Activate(); - } - } - - /// - /// Invoked when Navigation to a certain page fails - /// - /// The Frame which failed navigation - /// Details about the navigation failure - void OnNavigationFailed(object sender, NavigationFailedEventArgs e) - { - throw new Exception("Failed to load Page " + e.SourcePageType.FullName); - } - - /// - /// Invoked when application execution is being suspended. Application state is saved - /// without knowing whether the application will be terminated or resumed with the contents - /// of memory still intact. - /// - /// The source of the suspend request. - /// Details about the suspend request. - private void OnSuspending(object sender, SuspendingEventArgs e) - { - var deferral = e.SuspendingOperation.GetDeferral(); - //TODO: Save application state and stop any background activity - deferral.Complete(); - } + InitializeComponent(); } + + protected override void OnActivated(IActivatedEventArgs args) + { + if (args.Kind == ActivationKind.Protocol) + { + ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs; + var publisher = Mvx.IoCProvider.Resolve(); + publisher.ReceiveToken(eventArgs.Uri); + } + } + } } diff --git a/SpotifyAPI.Web.Examples/Example.UWP/CoreApp.cs b/SpotifyAPI.Web.Examples/Example.UWP/CoreApp.cs new file mode 100644 index 00000000..9902a714 --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.UWP/CoreApp.cs @@ -0,0 +1,19 @@ +using Example.UWP.ViewModels; +using MvvmCross.IoC; +using MvvmCross.ViewModels; + +namespace Example.UWP +{ + public class CoreApp : MvxApplication + { + public override void Initialize() + { + CreatableTypes() + .EndingWith("Service") + .AsInterfaces() + .RegisterAsLazySingleton(); + + RegisterAppStart(); + } + } +} diff --git a/SpotifyAPI.Web.Examples/Example.UWP/Example.UWP.csproj b/SpotifyAPI.Web.Examples/Example.UWP/Example.UWP.csproj index 8db0e9cc..c963bd88 100644 --- a/SpotifyAPI.Web.Examples/Example.UWP/Example.UWP.csproj +++ b/SpotifyAPI.Web.Examples/Example.UWP/Example.UWP.csproj @@ -4,11 +4,11 @@ Debug x86 - {8968B88F-3A6B-4012-8449-B502C7CD658A} + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70} AppContainerExe Properties - UWP - UWP + Example.UWP + Example.UWP en-US UAP 10.0.18362.0 @@ -119,10 +119,17 @@ App.xaml - - MainPage.xaml - + + + + + + LoginView.xaml + + + PlaylistsListView.xaml + @@ -144,15 +151,30 @@ MSBuild:Compile Designer - - MSBuild:Compile - Designer - 6.2.9 + + 6.4.2 + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + {ec8a93ba-27c4-4642-b7a0-11b809c35be4} + SpotifyAPI.Web + 14.0 diff --git a/SpotifyAPI.Web.Examples/Example.UWP/MainPage.xaml b/SpotifyAPI.Web.Examples/Example.UWP/MainPage.xaml deleted file mode 100644 index 84656e1e..00000000 --- a/SpotifyAPI.Web.Examples/Example.UWP/MainPage.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/SpotifyAPI.Web.Examples/Example.UWP/MainPage.xaml.cs b/SpotifyAPI.Web.Examples/Example.UWP/MainPage.xaml.cs deleted file mode 100644 index e2a9b8c9..00000000 --- a/SpotifyAPI.Web.Examples/Example.UWP/MainPage.xaml.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Navigation; - -// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 - -namespace Example.UWP -{ - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// - public sealed partial class MainPage : Page - { - public MainPage() - { - this.InitializeComponent(); - } - } -} diff --git a/SpotifyAPI.Web.Examples/Example.UWP/Package.appxmanifest b/SpotifyAPI.Web.Examples/Example.UWP/Package.appxmanifest index d0baed70..131d2467 100644 --- a/SpotifyAPI.Web.Examples/Example.UWP/Package.appxmanifest +++ b/SpotifyAPI.Web.Examples/Example.UWP/Package.appxmanifest @@ -7,14 +7,14 @@ IgnorableNamespaces="uap mp"> - + - UWP + Example.UWP jonas Assets\StoreLogo.png @@ -35,11 +35,19 @@ DisplayName="Example.UWP" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" - Description="UWP" + Description="Example.UWP" BackgroundColor="transparent"> + + + + Assets\StoreLogo.png + SpotifyAPI.Example.UWP + + + diff --git a/SpotifyAPI.Web.Examples/Example.UWP/Properties/AssemblyInfo.cs b/SpotifyAPI.Web.Examples/Example.UWP/Properties/AssemblyInfo.cs index caff9eed..e8374d79 100644 --- a/SpotifyAPI.Web.Examples/Example.UWP/Properties/AssemblyInfo.cs +++ b/SpotifyAPI.Web.Examples/Example.UWP/Properties/AssemblyInfo.cs @@ -5,11 +5,11 @@ using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("UWP")] +[assembly: AssemblyTitle("Example.UWP")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("UWP")] +[assembly: AssemblyProduct("Example.UWP")] [assembly: AssemblyCopyright("Copyright © 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/SpotifyAPI.Web.Examples/Example.UWP/TokenPublisherService.cs b/SpotifyAPI.Web.Examples/Example.UWP/TokenPublisherService.cs new file mode 100644 index 00000000..1add740d --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.UWP/TokenPublisherService.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Example.UWP +{ + public interface ITokenPublisherService + { + event EventHandler TokenReceived; + void ReceiveToken(Uri uri); + } + public class TokenPublisherService : ITokenPublisherService + { + public event EventHandler TokenReceived; + + public void ReceiveToken(Uri uri) + { + if(string.IsNullOrEmpty(uri.Fragment)) + { + throw new Exception($"Received weird URI: {uri}"); + } + var arguments = uri.Fragment.Substring(1).Split("&") + .Select(param => param.Split("=")) + .ToDictionary(param => param[0], param => param[1]); + + if(arguments["access_token"] == null) + { + throw new Exception($"No access token found in URI: {uri}"); + } + + TokenReceived?.Invoke(this, arguments["access_token"]); + } + } +} diff --git a/SpotifyAPI.Web.Examples/Example.UWP/ViewModels/LoginViewModel.cs b/SpotifyAPI.Web.Examples/Example.UWP/ViewModels/LoginViewModel.cs new file mode 100644 index 00000000..7d40af98 --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.UWP/ViewModels/LoginViewModel.cs @@ -0,0 +1,82 @@ +using System; +using System.Threading.Tasks; +using MvvmCross.Commands; +using MvvmCross.Navigation; +using MvvmCross.ViewModels; +using SpotifyAPI.Web; + +namespace Example.UWP.ViewModels +{ + public class LoginViewModel : MvxViewModel, IDisposable + { + public LoginViewModel(ITokenPublisherService tokenPublisher, IMvxNavigationService navigation) + { + _tokenPublisher = tokenPublisher; + _navigation = navigation; + } + + public override void ViewAppeared() + { + base.ViewAppeared(); + _tokenPublisher.TokenReceived += TokenPublisher_TokenReceived; + } + + public override void ViewDisappeared() + { + base.ViewDisappeared(); + _tokenPublisher.TokenReceived -= TokenPublisher_TokenReceived; + } + + public void Dispose() + { + _tokenPublisher.TokenReceived -= TokenPublisher_TokenReceived; + } + + private readonly ITokenPublisherService _tokenPublisher; + private readonly IMvxNavigationService _navigation; + + private string _titleText = "Spotify OAuth2 Login"; + public string TitleText + { + get => _titleText; + set => SetProperty(ref _titleText, value); + } + + private Uri _redirectUri = new Uri("spotifyapi.web.oauth://token"); + public Uri RedirectUri + { + get => _redirectUri; + set => SetProperty(ref _redirectUri, value); + } + + private string _clientId = ""; + public string ClientId + { + get => _clientId; + set { + if(SetProperty(ref _clientId, value)) + { + OpenAuthenticationPage.RaiseCanExecuteChanged(); + } + } + } + + private MvxCommand _openAuthenticationPage; + public MvxCommand OpenAuthenticationPage => _openAuthenticationPage ?? (_openAuthenticationPage = + new MvxCommand( + async () => await DoOpenAuthenticationPage(), + () => !string.IsNullOrEmpty(ClientId) + )); + + public async Task DoOpenAuthenticationPage() + { + var request = new LoginRequest(new Uri("spotifyapi.web.oauth://token"), ClientId, LoginRequest.ResponseType.Token); + await Windows.System.Launcher.LaunchUriAsync(request.ToUri()); + } + + private void TokenPublisher_TokenReceived(object sender, string token) + { + _navigation.Navigate(token); + } + } +} diff --git a/SpotifyAPI.Web.Examples/Example.UWP/ViewModels/PlaylistsListViewModel.cs b/SpotifyAPI.Web.Examples/Example.UWP/ViewModels/PlaylistsListViewModel.cs new file mode 100644 index 00000000..47901481 --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.UWP/ViewModels/PlaylistsListViewModel.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MvvmCross.ViewModels; +using SpotifyAPI.Web; + +namespace Example.UWP.ViewModels +{ + /// + /// Spotify Token is the parameter + /// + public class PlaylistsListViewModel : MvxViewModel + { + private SpotifyClient _spotify; + + private List _playlists; + public List Playlists { + get => _playlists ?? (_playlists = new List()); + set => SetProperty(ref _playlists, value); + } + + public override void Prepare(string token) + { + _spotify = new SpotifyClient(SpotifyClientConfig.CreateDefault(token)); + } + + public override async Task Initialize() + { + await base.Initialize(); + + Playlists = await _spotify.Paginate(() => _spotify.Playlists.CurrentUsers()); + } + } +} diff --git a/SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml b/SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml new file mode 100644 index 00000000..bb551a5b --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml.cs b/SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml.cs new file mode 100644 index 00000000..981ab9aa --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml.cs @@ -0,0 +1,26 @@ +using Example.UWP.ViewModels; +using MvvmCross.Platforms.Uap.Presenters.Attributes; +using MvvmCross.Platforms.Uap.Views; +using MvvmCross.ViewModels; + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace Example.UWP.Views +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + [MvxViewFor(typeof(LoginViewModel))] + [MvxPagePresentation] + public sealed partial class LoginView : LoginViewPage + { + public LoginView() + { + InitializeComponent(); + } + } + + public abstract class LoginViewPage : MvxWindowsPage + { + } +} diff --git a/SpotifyAPI.Web.Examples/Example.UWP/Views/PlaylistsListView.xaml b/SpotifyAPI.Web.Examples/Example.UWP/Views/PlaylistsListView.xaml new file mode 100644 index 00000000..6220b91d --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.UWP/Views/PlaylistsListView.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpotifyAPI.Web.Examples/Example.UWP/Views/PlaylistsListView.xaml.cs b/SpotifyAPI.Web.Examples/Example.UWP/Views/PlaylistsListView.xaml.cs new file mode 100644 index 00000000..050d739d --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.UWP/Views/PlaylistsListView.xaml.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Example.UWP.ViewModels; +using MvvmCross.Platforms.Uap.Presenters.Attributes; +using MvvmCross.Platforms.Uap.Views; +using MvvmCross.ViewModels; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace Example.UWP.Views +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + [MvxViewFor(typeof(PlaylistsListViewModel))] + [MvxPagePresentation] + public sealed partial class PlaylistsListView : PlaylistsListViewPage + { + public PlaylistsListView() + { + InitializeComponent(); + } + } + + public abstract class PlaylistsListViewPage : MvxWindowsPage + { + } +} diff --git a/SpotifyAPI.Web/Models/Request/LoginRequest.cs b/SpotifyAPI.Web/Models/Request/LoginRequest.cs new file mode 100644 index 00000000..1da7bbcc --- /dev/null +++ b/SpotifyAPI.Web/Models/Request/LoginRequest.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Web; + +namespace SpotifyAPI.Web +{ + public class LoginRequest + { + public LoginRequest(Uri redirectUri, string clientId, ResponseType responseType) + { + Ensure.ArgumentNotNull(redirectUri, nameof(redirectUri)); + Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId)); + + RedirectUri = redirectUri; + ClientId = clientId; + ResponseTypeParam = responseType; + } + + public Uri RedirectUri { get; } + public ResponseType ResponseTypeParam { get; } + public string ClientId { get; } + public string State { get; set; } + public ICollection Scope { get; set; } + public bool? ShowDialog { get; set; } + + public Uri ToUri() + { + StringBuilder builder = new StringBuilder(SpotifyUrls.Authorize.ToString()); + builder.Append($"?client_id={ClientId}"); + builder.Append($"&response_type={ResponseTypeParam.ToString().ToLower()}"); + builder.Append($"&redirect_uri={HttpUtility.UrlEncode(RedirectUri.ToString())}"); + if (!string.IsNullOrEmpty(State)) + { + builder.Append($"&state={HttpUtility.UrlEncode(State)}"); + } + if (Scope != null) + { + builder.Append($"&scope={HttpUtility.UrlEncode(string.Join(" ", Scope))}"); + } + if (ShowDialog != null) + { + builder.Append($"&show_dialog={ShowDialog.Value}"); + } + + return new Uri(builder.ToString()); + } + + public enum ResponseType + { + Code, + Token + } + } +} diff --git a/SpotifyAPI.sln b/SpotifyAPI.sln index b3b7c618..91a2c1aa 100644 --- a/SpotifyAPI.sln +++ b/SpotifyAPI.sln @@ -20,7 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.UWP", "SpotifyAPI.Web.Examples\Example.UWP\Example.UWP.csproj", "{8968B88F-3A6B-4012-8449-B502C7CD658A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.UWP", "SpotifyAPI.Web.Examples\Example.UWP\Example.UWP.csproj", "{70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -136,32 +136,32 @@ Global {941AB88D-B3A9-407F-BF88-BFE14B401687}.Release|x64.Build.0 = Release|Any CPU {941AB88D-B3A9-407F-BF88-BFE14B401687}.Release|x86.ActiveCfg = Release|Any CPU {941AB88D-B3A9-407F-BF88-BFE14B401687}.Release|x86.Build.0 = Release|Any CPU - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|Any CPU.ActiveCfg = Debug|x86 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|ARM.ActiveCfg = Debug|ARM - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|ARM.Build.0 = Debug|ARM - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|ARM.Deploy.0 = Debug|ARM - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|ARM64.Build.0 = Debug|ARM64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|x64.ActiveCfg = Debug|x64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|x64.Build.0 = Debug|x64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|x64.Deploy.0 = Debug|x64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|x86.ActiveCfg = Debug|x86 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|x86.Build.0 = Debug|x86 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Debug|x86.Deploy.0 = Debug|x86 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|Any CPU.ActiveCfg = Release|x86 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|ARM.ActiveCfg = Release|ARM - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|ARM.Build.0 = Release|ARM - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|ARM.Deploy.0 = Release|ARM - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|ARM64.ActiveCfg = Release|ARM64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|ARM64.Build.0 = Release|ARM64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|ARM64.Deploy.0 = Release|ARM64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|x64.ActiveCfg = Release|x64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|x64.Build.0 = Release|x64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|x64.Deploy.0 = Release|x64 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|x86.ActiveCfg = Release|x86 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|x86.Build.0 = Release|x86 - {8968B88F-3A6B-4012-8449-B502C7CD658A}.Release|x86.Deploy.0 = Release|x86 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|Any CPU.ActiveCfg = Debug|x86 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|ARM.ActiveCfg = Debug|ARM + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|ARM.Build.0 = Debug|ARM + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|ARM.Deploy.0 = Debug|ARM + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|ARM64.Build.0 = Debug|ARM64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|x64.ActiveCfg = Debug|x64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|x64.Build.0 = Debug|x64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|x64.Deploy.0 = Debug|x64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|x86.ActiveCfg = Debug|x86 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|x86.Build.0 = Debug|x86 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Debug|x86.Deploy.0 = Debug|x86 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|Any CPU.ActiveCfg = Release|x86 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|ARM.ActiveCfg = Release|ARM + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|ARM.Build.0 = Release|ARM + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|ARM.Deploy.0 = Release|ARM + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|ARM64.ActiveCfg = Release|ARM64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|ARM64.Build.0 = Release|ARM64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|ARM64.Deploy.0 = Release|ARM64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|x64.ActiveCfg = Release|x64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|x64.Build.0 = Release|x64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|x64.Deploy.0 = Release|x64 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|x86.ActiveCfg = Release|x86 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|x86.Build.0 = Release|x86 + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}.Release|x86.Deploy.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -169,7 +169,7 @@ Global GlobalSection(NestedProjects) = preSolution {F4ECE937-99F2-4C4F-9F5C-4AB875D9538A} = {48A7DE65-29BB-409C-AC45-77F6586C0B15} {941AB88D-B3A9-407F-BF88-BFE14B401687} = {48A7DE65-29BB-409C-AC45-77F6586C0B15} - {8968B88F-3A6B-4012-8449-B502C7CD658A} = {48A7DE65-29BB-409C-AC45-77F6586C0B15} + {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70} = {48A7DE65-29BB-409C-AC45-77F6586C0B15} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {097062B8-0E87-43C8-BD98-61843A68BE6D}