Update version

This commit is contained in:
Daniel Clark
2021-02-15 21:21:50 +00:00
parent b96155be73
commit 9fa476e57d
25 changed files with 726 additions and 68 deletions
+253
View File
@@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Windows.System;
namespace NotificationFlyoutSample
{
public static class DispatcherQueueExtensions
{
/// <summary>
/// Indicates whether or not <see cref="DispatcherQueue.HasThreadAccess"/> is available.
/// </summary>
private static bool IsHasThreadAccessPropertyAvailable => true;
public static Task EnqueueAsync(this DispatcherQueue dispatcher, Action function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
// Run the function directly when we have thread access.
// Also reuse Task.CompletedTask in case of success,
// to skip an unnecessary heap allocation for every invocation.
if (IsHasThreadAccessPropertyAvailable && dispatcher.HasThreadAccess)
{
try
{
function();
return Task.CompletedTask;
}
catch (Exception e)
{
return Task.FromException(e);
}
}
static Task TryEnqueueAsync(DispatcherQueue dispatcher, Action function, DispatcherQueuePriority priority)
{
var taskCompletionSource = new TaskCompletionSource<object?>();
if (!dispatcher.TryEnqueue(priority, () =>
{
try
{
function();
taskCompletionSource.SetResult(null);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
}))
{
taskCompletionSource.SetException(GetEnqueueException("Failed to enqueue the operation"));
}
return taskCompletionSource.Task;
}
return TryEnqueueAsync(dispatcher, function, priority);
}
/// <summary>
/// Invokes a given function on the target <see cref="DispatcherQueue"/> and returns a
/// <see cref="Task{TResult}"/> that completes when the invocation of the function is completed.
/// </summary>
/// <typeparam name="T">The return type of <paramref name="function"/> to relay through the returned <see cref="Task{TResult}"/>.</typeparam>
/// <param name="dispatcher">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
/// <param name="function">The <see cref="Func{TResult}"/> to invoke.</param>
/// <param name="priority">The priority level for the function to invoke.</param>
/// <returns>A <see cref="Task"/> that completes when the invocation of <paramref name="function"/> is over.</returns>
/// <remarks>If the current thread has access to <paramref name="dispatcher"/>, <paramref name="function"/> will be invoked directly.</remarks>
public static Task<T> EnqueueAsync<T>(this DispatcherQueue dispatcher, Func<T> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
if (IsHasThreadAccessPropertyAvailable && dispatcher.HasThreadAccess)
{
try
{
return Task.FromResult(function());
}
catch (Exception e)
{
return Task.FromException<T>(e);
}
}
static Task<T> TryEnqueueAsync(DispatcherQueue dispatcher, Func<T> function, DispatcherQueuePriority priority)
{
var taskCompletionSource = new TaskCompletionSource<T>();
if (!dispatcher.TryEnqueue(priority, () =>
{
try
{
taskCompletionSource.SetResult(function());
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
}))
{
taskCompletionSource.SetException(GetEnqueueException("Failed to enqueue the operation"));
}
return taskCompletionSource.Task;
}
return TryEnqueueAsync(dispatcher, function, priority);
}
/// <summary>
/// Invokes a given function on the target <see cref="DispatcherQueue"/> and returns a
/// <see cref="Task"/> that acts as a proxy for the one returned by the given function.
/// </summary>
/// <param name="dispatcher">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
/// <param name="function">The <see cref="Func{TResult}"/> to invoke.</param>
/// <param name="priority">The priority level for the function to invoke.</param>
/// <returns>A <see cref="Task"/> that acts as a proxy for the one returned by <paramref name="function"/>.</returns>
/// <remarks>If the current thread has access to <paramref name="dispatcher"/>, <paramref name="function"/> will be invoked directly.</remarks>
public static Task EnqueueAsync(this DispatcherQueue dispatcher, Func<Task> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
// If we have thread access, we can retrieve the task directly.
// We don't use ConfigureAwait(false) in this case, in order
// to let the caller continue its execution on the same thread
// after awaiting the task returned by this function.
if (IsHasThreadAccessPropertyAvailable && dispatcher.HasThreadAccess)
{
try
{
if (function() is Task awaitableResult)
{
return awaitableResult;
}
return Task.FromException(GetEnqueueException("The Task returned by function cannot be null."));
}
catch (Exception e)
{
return Task.FromException(e);
}
}
static Task TryEnqueueAsync(DispatcherQueue dispatcher, Func<Task> function, DispatcherQueuePriority priority)
{
var taskCompletionSource = new TaskCompletionSource<object?>();
if (!dispatcher.TryEnqueue(priority, async () =>
{
try
{
if (function() is Task awaitableResult)
{
await awaitableResult.ConfigureAwait(false);
taskCompletionSource.SetResult(null);
}
else
{
taskCompletionSource.SetException(GetEnqueueException("The Task returned by function cannot be null."));
}
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
}))
{
taskCompletionSource.SetException(GetEnqueueException("Failed to enqueue the operation"));
}
return taskCompletionSource.Task;
}
return TryEnqueueAsync(dispatcher, function, priority);
}
/// <summary>
/// Invokes a given function on the target <see cref="DispatcherQueue"/> and returns a
/// <see cref="Task{TResult}"/> that acts as a proxy for the one returned by the given function.
/// </summary>
/// <typeparam name="T">The return type of <paramref name="function"/> to relay through the returned <see cref="Task{TResult}"/>.</typeparam>
/// <param name="dispatcher">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
/// <param name="function">The <see cref="Func{TResult}"/> to invoke.</param>
/// <param name="priority">The priority level for the function to invoke.</param>
/// <returns>A <see cref="Task{TResult}"/> that relays the one returned by <paramref name="function"/>.</returns>
/// <remarks>If the current thread has access to <paramref name="dispatcher"/>, <paramref name="function"/> will be invoked directly.</remarks>
public static Task<T> EnqueueAsync<T>(this DispatcherQueue dispatcher, Func<Task<T>> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
if (IsHasThreadAccessPropertyAvailable && dispatcher.HasThreadAccess)
{
try
{
if (function() is Task<T> awaitableResult)
{
return awaitableResult;
}
return Task.FromException<T>(GetEnqueueException("The Task returned by function cannot be null."));
}
catch (Exception e)
{
return Task.FromException<T>(e);
}
}
static Task<T> TryEnqueueAsync(DispatcherQueue dispatcher, Func<Task<T>> function, DispatcherQueuePriority priority)
{
var taskCompletionSource = new TaskCompletionSource<T>();
if (!dispatcher.TryEnqueue(priority, async () =>
{
try
{
if (function() is Task<T> awaitableResult)
{
var result = await awaitableResult.ConfigureAwait(false);
taskCompletionSource.SetResult(result);
}
else
{
taskCompletionSource.SetException(GetEnqueueException("The Task returned by function cannot be null."));
}
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
}))
{
taskCompletionSource.SetException(GetEnqueueException("Failed to enqueue the operation"));
}
return taskCompletionSource.Task;
}
return TryEnqueueAsync(dispatcher, function, priority);
}
/// <summary>
/// Creates an <see cref="InvalidOperationException"/> to return when an enqueue operation fails.
/// </summary>
/// <param name="message">The message of the exception.</param>
/// <returns>An <see cref="InvalidOperationException"/> with a specified message.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private static InvalidOperationException GetEnqueueException(string message)
{
return new InvalidOperationException(message);
}
}
}
@@ -0,0 +1,7 @@
namespace NotificationFlyoutSample
{
public interface INavigation
{
void OnNavigatedTo();
}
}
@@ -119,9 +119,16 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Class1.cs" />
<Compile Include="INavigation.cs" />
<Compile Include="NowPlayingPage.xaml.cs">
<DependentUpon>NowPlayingPage.xaml</DependentUpon>
</Compile>
<Compile Include="NowPlayingPageViewModel.cs" />
<Compile Include="ObservableObject.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Shell.xaml.cs">
<DependentUpon>Shell.xaml</DependentUpon>
<Compile Include="NowPlayingFlyout.xaml.cs">
<DependentUpon>NowPlayingFlyout.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
@@ -158,14 +165,18 @@
<Version>6.1.2</Version>
</PackageReference>
<PackageReference Include="Microsoft.UI.Xaml">
<Version>2.6.0-prerelease.210129001</Version>
<Version>2.6.0-prerelease.210113001</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Page Include="Shell.xaml">
<Page Include="NowPlayingFlyout.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="NowPlayingPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\TheXamlGuy.NotificationFlyout.Uwp.UI.Controls\TheXamlGuy.NotificationFlyout.Uwp.UI.Controls.csproj">
@@ -192,5 +203,6 @@
<EnableTypeInfoReflection>false</EnableTypeInfoReflection>
<EnableXBindDiagnostics>false</EnableXBindDiagnostics>
<MSBuildWarningsAsMessages>CS8305</MSBuildWarningsAsMessages>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
</Project>
@@ -0,0 +1,24 @@
<controls:NotificationFlyout
x:Class="NotificationFlyoutSample.NowPlayingFlyout"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:TheXamlGuy.NotificationFlyout.Uwp.UI.Controls"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
IconSource="/Assets/Notification-Icon-Playing.png"
LightIconSource="/Assets/Notification-Icon-Playing-Light.png">
<controls:NotificationFlyout.FlyoutPresenterStyle>
<Style TargetType="controls:NotificationFlyoutPresenter">
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Margin" Value="8" />
</Style>
</controls:NotificationFlyout.FlyoutPresenterStyle>
<controls:NotificationFlyout.ContextMenu>
<controls:NotificationFlyoutContextMenu>
<MenuFlyoutItem Click="OnCloseMenuFlyoutItemClick" Text="Close" />
</controls:NotificationFlyoutContextMenu>
</controls:NotificationFlyout.ContextMenu>
<Frame
x:Name="RootFrame"
Width="400"
Margin="24" />
</controls:NotificationFlyout>
@@ -0,0 +1,24 @@
using Windows.UI.Xaml;
namespace NotificationFlyoutSample
{
public sealed partial class NowPlayingFlyout
{
public NowPlayingFlyout()
{
InitializeComponent();
Opened += OnOpened;
}
private void OnOpened(object sender, object args)
{
RootFrame.Navigate(typeof(NowPlayingPage));
}
private void OnCloseMenuFlyoutItemClick(object sender, RoutedEventArgs args)
{
var app = GetApplication();
app.Exit();
}
}
}
@@ -1,25 +1,11 @@
<controls:NotificationFlyout
x:Class="NotificationFlyoutSample.Shell"
<Page
x:Class="NotificationFlyoutSample.NowPlayingPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:TheXamlGuy.NotificationFlyout.Uwp.UI.Controls"
IconSource="/Assets/Notification-Icon-Playing.png"
LightIconSource="/Assets/Notification-Icon-Playing-Light.png">
<controls:NotificationFlyout.FlyoutPresenterStyle>
<Style TargetType="controls:NotificationFlyoutPresenter">
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Margin" Value="8" />
</Style>
</controls:NotificationFlyout.FlyoutPresenterStyle>
<controls:NotificationFlyout.ContextMenu>
<controls:NotificationFlyoutContextMenu>
<MenuFlyoutItem Click="MenuFlyoutItem_Click" Text="Close" />
</controls:NotificationFlyoutContextMenu>
</controls:NotificationFlyout.ContextMenu>
<Grid Width="400" Margin="24">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="42" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
@@ -34,11 +20,11 @@
Height="94"
Background="Gray"
CornerRadius="4">
<Image Source="/Assets/album.jpg" Stretch="UniformToFill" />
<Image Source="{x:Bind ViewModel.Thumbnail, Mode=OneWay}" Stretch="UniformToFill" />
</Border>
<StackPanel Grid.Column="1" Margin="12,12,0,0">
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="Foo Fighters" />
<TextBlock Text="Best of you" />
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{x:Bind ViewModel.Artist, Mode=TwoWay}" />
<TextBlock Text="{x:Bind ViewModel.Song, Mode=TwoWay}" />
</StackPanel>
</Grid>
<Slider Grid.Row="1" />
@@ -51,21 +37,44 @@
HorizontalAlignment="Center"
Background="Transparent"
DefaultLabelPosition="Collapsed">
<AppBarButton Width="48" CornerRadius="4">
<AppBarButton
Width="48"
Click="{x:Bind ViewModel.Previous}"
CornerRadius="4"
IsCompact="True">
<AppBarButton.Icon>
<FontIcon Glyph="&#xE100;" />
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton Width="48" CornerRadius="4">
<AppBarButton
Width="48"
Click="{x:Bind ViewModel.Play}"
CornerRadius="4"
IsCompact="True"
Visibility="{x:Bind ViewModel.IsPaused, Mode=OneWay}">
<AppBarButton.Icon>
<FontIcon Glyph="&#xE102;" />
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton Width="48" CornerRadius="4">
<AppBarButton
Width="48"
Click="{x:Bind ViewModel.Pause}"
CornerRadius="4"
IsCompact="True"
Visibility="{x:Bind ViewModel.IsPlaying, Mode=OneWay}">
<AppBarButton.Icon>
<FontIcon Glyph="&#xE103;" />
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton
Width="48"
Click="{x:Bind ViewModel.Next}"
CornerRadius="4"
IsCompact="True">
<AppBarButton.Icon>
<FontIcon Glyph="&#xE101;" />
</AppBarButton.Icon>
</AppBarButton>
</CommandBar>
</Grid>
</controls:NotificationFlyout>
</Page>
@@ -0,0 +1,29 @@
using System;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace NotificationFlyoutSample
{
public sealed partial class NowPlayingPage : Page
{
public NowPlayingPage()
{
InitializeComponent();
ViewModel = new NowPlayingPageViewModel();
DataContext = ViewModel;
}
public NowPlayingPageViewModel ViewModel { get; }
protected override void OnNavigatedTo(NavigationEventArgs args)
{
if (DataContext is INavigation navigation)
{
navigation.OnNavigatedTo();
}
base.OnNavigatedTo(args);
}
}
}
@@ -0,0 +1,121 @@
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Media.Control;
using Windows.Storage.Streams;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Xaml.Media.Imaging;
namespace NotificationFlyoutSample
{
public class NowPlayingPageViewModel : ObservableObject, INavigation
{
private string _artist;
private bool _isPlaying;
private bool _isPaused;
private GlobalSystemMediaTransportControlsSession _session;
private string _song;
private BitmapImage _thumbnail;
public bool IsPaused
{
get => _isPaused;
set => SetProperty(ref _isPaused, value);
}
public string Artist
{
get => _artist;
set => SetProperty(ref _artist, value);
}
public bool IsPlaying
{
get => _isPlaying;
set => SetProperty(ref _isPlaying, value);
}
public string Song
{
get => _song;
set => SetProperty(ref _song, value);
}
public async Task Next()
{
await _session.TrySkipNextAsync();
}
DispatcherQueue d;
public async void OnNavigatedTo()
{
d = DispatcherQueue.GetForCurrentThread();
var sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
_session = sessionManager.GetCurrentSession();
if (_session != null)
{
_session.MediaPropertiesChanged += OnMediaPropertiesChanged;
}
}
public BitmapImage Thumbnail
{
get => _thumbnail;
set => SetProperty(ref _thumbnail, value);
}
private async void OnMediaPropertiesChanged(GlobalSystemMediaTransportControlsSession sender, MediaPropertiesChangedEventArgs args)
{
var mediaProperties = await _session.TryGetMediaPropertiesAsync();
await d.EnqueueAsync(async () => {
Artist = mediaProperties.Artist;
var foo = mediaProperties.Thumbnail;
if (foo != null)
{
var f = await foo.OpenReadAsync();
var image = new BitmapImage();
await image.SetSourceAsync(f);
Thumbnail = image;
}
});
}
public async Task Pause()
{
var result = await _session.TryPauseAsync().AsTask().ConfigureAwait(false);
if (result)
{
IsPlaying = false;
}
}
public async Task Play()
{
await _session.TryPlayAsync().AsTask().ConfigureAwait(false);
}
public async Task Previous()
{
await _session.TrySkipPreviousAsync().AsTask().ConfigureAwait(false);
}
private async Task UpdateNowPlaying()
{
var playbackInfo = _session.GetPlaybackInfo();
var mediaProperties = await _session.TryGetMediaPropertiesAsync();
Artist = mediaProperties.Artist;
Song = mediaProperties.Title;
}
}
}
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace NotificationFlyoutSample
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected virtual bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string propertyName = "",
Action onChanged = null, Func<T, T, bool> validateValue = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
if (validateValue != null && !validateValue(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
}
}
@@ -1,21 +0,0 @@
using System;
using System.Threading.Tasks;
using Windows.Media.Control;
namespace NotificationFlyoutSample
{
public sealed partial class Shell
{
public Shell()
{
InitializeComponent();
}
private void MenuFlyoutItem_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
var app = GetApplication();
app.Exit();
}
}
}