mirror of
https://github.com/Sarsoo/Spotify.NET.git
synced 2024-12-23 14:46:26 +00:00
Finished UWP Example using protocol registration - Docs TODO
This commit is contained in:
parent
d095a5a870
commit
24bb3d345f
@ -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);
|
||||
|
@ -12,8 +12,6 @@ namespace SpotifyAPI.Web.Auth
|
||||
Task Start();
|
||||
Task Stop();
|
||||
|
||||
Uri BuildLoginUri(LoginRequest request);
|
||||
|
||||
Uri BaseUri { get; }
|
||||
}
|
||||
}
|
||||
|
@ -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<string> Scope { get; set; }
|
||||
public bool? ShowDialog { get; set; }
|
||||
|
||||
public enum ResponseType
|
||||
{
|
||||
Code,
|
||||
Token
|
||||
}
|
||||
}
|
||||
}
|
@ -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<string> { 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();
|
||||
|
@ -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<string> { 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
<Application
|
||||
x:Class="UWP.App"
|
||||
<local:ExampleApp x:Class="Example.UWP.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:UWP">
|
||||
|
||||
</Application>
|
||||
xmlns:local="using:Example.UWP"
|
||||
RequestedTheme="Dark">
|
||||
<Application.Resources>
|
||||
<x:String x:Key="WelcomeText">Hello World!</x:String>
|
||||
</Application.Resources>
|
||||
</local:ExampleApp>
|
||||
|
@ -1,26 +1,24 @@
|
||||
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<MvxWindowsSetup<CoreApp>, CoreApp>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
sealed partial class App : Application
|
||||
public sealed partial class App
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
@ -28,73 +26,17 @@ namespace Example.UWP
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.Suspending += OnSuspending;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="e">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs e)
|
||||
protected override void OnActivated(IActivatedEventArgs args)
|
||||
{
|
||||
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)
|
||||
if (args.Kind == ActivationKind.Protocol)
|
||||
{
|
||||
// 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
|
||||
ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
|
||||
var publisher = Mvx.IoCProvider.Resolve<ITokenPublisherService>();
|
||||
publisher.ReceiveToken(eventArgs.Uri);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when Navigation to a certain page fails
|
||||
/// </summary>
|
||||
/// <param name="sender">The Frame which failed navigation</param>
|
||||
/// <param name="e">Details about the navigation failure</param>
|
||||
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the suspend request.</param>
|
||||
/// <param name="e">Details about the suspend request.</param>
|
||||
private void OnSuspending(object sender, SuspendingEventArgs e)
|
||||
{
|
||||
var deferral = e.SuspendingOperation.GetDeferral();
|
||||
//TODO: Save application state and stop any background activity
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
SpotifyAPI.Web.Examples/Example.UWP/CoreApp.cs
Normal file
19
SpotifyAPI.Web.Examples/Example.UWP/CoreApp.cs
Normal file
@ -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<LoginViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,11 +4,11 @@
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProjectGuid>{8968B88F-3A6B-4012-8449-B502C7CD658A}</ProjectGuid>
|
||||
<ProjectGuid>{70D529B5-CEC0-4D3D-B50D-8ECF921DEC70}</ProjectGuid>
|
||||
<OutputType>AppContainerExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>UWP</RootNamespace>
|
||||
<AssemblyName>UWP</AssemblyName>
|
||||
<RootNamespace>Example.UWP</RootNamespace>
|
||||
<AssemblyName>Example.UWP</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.18362.0</TargetPlatformVersion>
|
||||
@ -119,10 +119,17 @@
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="MainPage.xaml.cs">
|
||||
<DependentUpon>MainPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="CoreApp.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TokenPublisherService.cs" />
|
||||
<Compile Include="ViewModels\LoginViewModel.cs" />
|
||||
<Compile Include="ViewModels\PlaylistsListViewModel.cs" />
|
||||
<Compile Include="Views\LoginView.xaml.cs">
|
||||
<DependentUpon>LoginView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\PlaylistsListView.xaml.cs">
|
||||
<DependentUpon>PlaylistsListView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
@ -144,15 +151,30 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Page Include="MainPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.2.9</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MvvmCross">
|
||||
<Version>6.4.2</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="Views\LoginView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\PlaylistsListView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SpotifyAPI.Web\SpotifyAPI.Web.csproj">
|
||||
<Project>{ec8a93ba-27c4-4642-b7a0-11b809c35be4}</Project>
|
||||
<Name>SpotifyAPI.Web</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
|
@ -1,14 +0,0 @@
|
||||
<Page
|
||||
x:Class="UWP.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:UWP"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
public MainPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,14 +7,14 @@
|
||||
IgnorableNamespaces="uap mp">
|
||||
|
||||
<Identity
|
||||
Name="ca23f8da-8ef6-406e-8234-ba506f52edc2"
|
||||
Name="ad405b02-e8d1-4972-9f4c-d0741ac5f355"
|
||||
Publisher="CN=jonas"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca23f8da-8ef6-406e-8234-ba506f52edc2" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
<mp:PhoneIdentity PhoneProductId="ad405b02-e8d1-4972-9f4c-d0741ac5f355" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>UWP</DisplayName>
|
||||
<DisplayName>Example.UWP</DisplayName>
|
||||
<PublisherDisplayName>jonas</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
@ -35,11 +35,19 @@
|
||||
DisplayName="Example.UWP"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png"
|
||||
Description="UWP"
|
||||
Description="Example.UWP"
|
||||
BackgroundColor="transparent">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="spotifyapi.web.oauth">
|
||||
<uap:Logo>Assets\StoreLogo.png</uap:Logo>
|
||||
<uap:DisplayName>SpotifyAPI.Example.UWP</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
|
@ -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("")]
|
||||
|
36
SpotifyAPI.Web.Examples/Example.UWP/TokenPublisherService.cs
Normal file
36
SpotifyAPI.Web.Examples/Example.UWP/TokenPublisherService.cs
Normal file
@ -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<string> TokenReceived;
|
||||
void ReceiveToken(Uri uri);
|
||||
}
|
||||
public class TokenPublisherService : ITokenPublisherService
|
||||
{
|
||||
public event EventHandler<string> 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"]);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<PlaylistsListViewModel, string>(token);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Spotify Token is the parameter
|
||||
/// </summary>
|
||||
public class PlaylistsListViewModel : MvxViewModel<string>
|
||||
{
|
||||
private SpotifyClient _spotify;
|
||||
|
||||
private List<SimplePlaylist> _playlists;
|
||||
public List<SimplePlaylist> Playlists {
|
||||
get => _playlists ?? (_playlists = new List<SimplePlaylist>());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
71
SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml
Normal file
71
SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml
Normal file
@ -0,0 +1,71 @@
|
||||
<app:LoginViewPage x:Class="Example.UWP.Views.LoginView"
|
||||
d:DataContext="{d:DesignInstance Type=models:LoginViewModel, IsDesignTimeCreatable=True}"
|
||||
xmlns:app="using:Example.UWP.Views"
|
||||
xmlns:models="using:Example.UWP.ViewModels"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Background="#353b48">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Margin="0,50"
|
||||
Grid.Row="0"
|
||||
TextAlignment="Center"
|
||||
Text="{Binding TitleText}"
|
||||
FontSize="45" />
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Text="Redirect URI:"
|
||||
VerticalAlignment="Top"
|
||||
Margin="50,20"
|
||||
FontSize="25"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBox Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Margin="20"
|
||||
Text="{Binding RedirectUri, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
FontSize="25"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top" />
|
||||
|
||||
<TextBlock Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Text="Spotify Client ID:"
|
||||
VerticalAlignment="Top"
|
||||
Margin="50,20"
|
||||
FontSize="25"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBox Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Margin="20"
|
||||
FontSize="25"
|
||||
Text="{Binding ClientId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top" />
|
||||
</Grid>
|
||||
<Button Grid.Row="2"
|
||||
Command="{Binding OpenAuthenticationPage}"
|
||||
Background="#1db954"
|
||||
FontSize="25"
|
||||
Padding="50,20"
|
||||
HorizontalAlignment="Center">
|
||||
Login
|
||||
</Button>
|
||||
</Grid>
|
||||
</app:LoginViewPage>
|
26
SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml.cs
Normal file
26
SpotifyAPI.Web.Examples/Example.UWP/Views/LoginView.xaml.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
[MvxViewFor(typeof(LoginViewModel))]
|
||||
[MvxPagePresentation]
|
||||
public sealed partial class LoginView : LoginViewPage
|
||||
{
|
||||
public LoginView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class LoginViewPage : MvxWindowsPage<LoginViewModel>
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<app:PlaylistsListViewPage x:Class="Example.UWP.Views.PlaylistsListView"
|
||||
d:DataContext="{d:DesignInstance Type=models:PlaylistsListViewModel, IsDesignTimeCreatable=False}"
|
||||
xmlns:app="using:Example.UWP.Views"
|
||||
xmlns:models="using:Example.UWP.ViewModels"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ListView x:Name="FruitsList"
|
||||
Background="#353b48"
|
||||
ItemsSource="{Binding Playlists}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image Grid.Column="0" Margin="20">
|
||||
<Image.Source>
|
||||
<BitmapImage UriSource="{Binding Images[0].Url}" />
|
||||
</Image.Source>
|
||||
</Image>
|
||||
|
||||
<TextBlock Text="{Binding Name}"
|
||||
Margin="20"
|
||||
FontSize="30"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</app:PlaylistsListViewPage>
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
[MvxViewFor(typeof(PlaylistsListViewModel))]
|
||||
[MvxPagePresentation]
|
||||
public sealed partial class PlaylistsListView : PlaylistsListViewPage
|
||||
{
|
||||
public PlaylistsListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class PlaylistsListViewPage : MvxWindowsPage<PlaylistsListViewModel>
|
||||
{
|
||||
}
|
||||
}
|
55
SpotifyAPI.Web/Models/Request/LoginRequest.cs
Normal file
55
SpotifyAPI.Web/Models/Request/LoginRequest.cs
Normal file
@ -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<string> 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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}
|
||||
|
Loading…
Reference in New Issue
Block a user