diff --git a/Screenshots/Screen1.jpg b/Screenshots/Screen1.jpg index 560885c4..92966365 100644 Binary files a/Screenshots/Screen1.jpg and b/Screenshots/Screen1.jpg differ diff --git a/Screenshots/Screen2.jpg b/Screenshots/Screen2.jpg index 306e48e0..1317537f 100644 Binary files a/Screenshots/Screen2.jpg and b/Screenshots/Screen2.jpg differ diff --git a/Screenshots/Screen3.jpg b/Screenshots/Screen3.jpg index c66e030d..50049855 100644 Binary files a/Screenshots/Screen3.jpg and b/Screenshots/Screen3.jpg differ diff --git a/Screenshots/Screen4.jpg b/Screenshots/Screen4.jpg index 2d5b794c..ba2216df 100644 Binary files a/Screenshots/Screen4.jpg and b/Screenshots/Screen4.jpg differ diff --git a/Screenshots/Screen5.jpg b/Screenshots/Screen5.jpg index cb266ab4..771e85a1 100644 Binary files a/Screenshots/Screen5.jpg and b/Screenshots/Screen5.jpg differ diff --git a/Screenshots/Screen6.jpg b/Screenshots/Screen6.jpg index 6d85de30..c32db99d 100644 Binary files a/Screenshots/Screen6.jpg and b/Screenshots/Screen6.jpg differ diff --git a/Screenshots/ScreenXbox.jpg b/Screenshots/ScreenXbox.jpg new file mode 100644 index 00000000..a0b29793 Binary files /dev/null and b/Screenshots/ScreenXbox.jpg differ diff --git a/Sources/FluentMPC/FluentMPC.csproj b/Sources/FluentMPC/FluentMPC.csproj index 70e566c6..79f0cf15 100644 --- a/Sources/FluentMPC/FluentMPC.csproj +++ b/Sources/FluentMPC/FluentMPC.csproj @@ -137,7 +137,7 @@ - 5.0.1 + 5.0.2 1.1.1.4 @@ -146,16 +146,19 @@ 6.2.9 - 6.1.1 + 7.0.0 - 6.1.1 + 7.0.0 - 6.1.1 + 7.0.0 - - 6.1.1 + + 7.0.0 + + + 7.0.0 2.5.0 @@ -164,7 +167,7 @@ 2.0.1 - 5.0.0 + 5.0.2 1.6.7 @@ -198,6 +201,7 @@ + diff --git a/Sources/FluentMPC/Package.appxmanifest b/Sources/FluentMPC/Package.appxmanifest index f8bbee02..d4fbddf1 100644 --- a/Sources/FluentMPC/Package.appxmanifest +++ b/Sources/FluentMPC/Package.appxmanifest @@ -12,7 +12,7 @@ + Version="1.6.0.0" /> diff --git a/Sources/FluentMPC/Package.tt b/Sources/FluentMPC/Package.tt index 783418b1..744f3732 100644 --- a/Sources/FluentMPC/Package.tt +++ b/Sources/FluentMPC/Package.tt @@ -2,7 +2,7 @@ <#@ output extension=".appxmanifest" #> <#@ parameter type="System.String" name="BuildConfiguration" #> <# - string version = "1.5.0.0"; + string version = "1.6.0.0"; // Get configuration name at Build time string configName = Host.ResolveParameterValue("-", "-", "BuildConfiguration"); diff --git a/Sources/FluentMPC/Services/AlbumArtService.cs b/Sources/FluentMPC/Services/AlbumArtService.cs index 92cee819..3b5e1604 100644 --- a/Sources/FluentMPC/Services/AlbumArtService.cs +++ b/Sources/FluentMPC/Services/AlbumArtService.cs @@ -1,6 +1,7 @@ using ColorThiefDotNet; using FluentMPC.Helpers; using FluentMPC.ViewModels.Items; +using Microsoft.Toolkit.Uwp; using Microsoft.Toolkit.Uwp.Helpers; using MpcNET.Commands.Database; using MpcNET.Types; @@ -14,6 +15,7 @@ using Windows.Graphics.Imaging; using Windows.Storage; using Windows.Storage.Streams; +using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml.Media.Imaging; @@ -101,21 +103,21 @@ public static async Task IsAlbumArtCachedAsync(IMpdFile f) /// Width of the final BitmapImage /// Dispatcher to use for the UI-bound options. Defaults to MainWindow.CoreWindow.Dispatcher. /// An AlbumArt object containing bitmap and color. Returns null if there was no albumart on the MPD server. - public async static Task GetAlbumArtAsync(IMpdFile f, bool calculateDominantColor, int albumArtWidth, CoreDispatcher dispatcher = null, CancellationToken token = default) + public async static Task GetAlbumArtAsync(IMpdFile f, bool calculateDominantColor, int albumArtWidth, DispatcherQueue dispatcherQueue = null, CancellationToken token = default) { - if (dispatcher == null) - dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher; + if (dispatcherQueue == null) + dispatcherQueue = DispatcherService.DispatcherQueue; var result = new AlbumArt(); - var bitmap = await GetAlbumBitmap(f, dispatcher, token); + var bitmap = await GetAlbumBitmap(f, dispatcherQueue, token); if (bitmap != null) { - result.ArtBitmap = await WriteableBitmapToBitmapImageAsync(bitmap, albumArtWidth, dispatcher); + result.ArtBitmap = await WriteableBitmapToBitmapImageAsync(bitmap, albumArtWidth, dispatcherQueue); if (calculateDominantColor) { - result.DominantColor = await GetDominantColor(bitmap, dispatcher); + result.DominantColor = await GetDominantColor(bitmap, dispatcherQueue); } return result; @@ -133,7 +135,7 @@ private static string GetFileIdentifier(IMpdFile f) return MiscHelpers.EscapeFilename(uniqueIdentifier); } - private async static Task GetAlbumBitmap(IMpdFile f, CoreDispatcher dispatcher, CancellationToken token = default) + private async static Task GetAlbumBitmap(IMpdFile f, DispatcherQueue dispatcher, CancellationToken token = default) { WriteableBitmap result; var foundUsableArt = false; @@ -208,12 +210,12 @@ private async static Task GetAlbumBitmap(IMpdFile f, CoreDispat return result; } - private static async Task GetDominantColor(WriteableBitmap art, CoreDispatcher dispatcher) + private static async Task GetDominantColor(WriteableBitmap art, DispatcherQueue dispatcherQueue) { // Get dominant color of albumart using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream()) { - await dispatcher.AwaitableRunAsync(async () => + await dispatcherQueue.EnqueueAsync(async () => { await art.Resize(50, 50, WriteableBitmapExtensions.Interpolation.NearestNeighbor).ToStreamAsJpeg(stream); }); @@ -232,10 +234,10 @@ private static async Task SaveArtToFileAsync(string fileName, List data) await pictureFolder.SaveFileAsync(data.ToArray(), fileName, CreationCollisionOption.ReplaceExisting); } - private static async Task WriteableBitmapToBitmapImageAsync(WriteableBitmap art, int decodedPixelWidth, CoreDispatcher dispatcher) + private static async Task WriteableBitmapToBitmapImageAsync(WriteableBitmap art, int decodedPixelWidth, DispatcherQueue dispatcherQueue) { BitmapImage image = null; - await dispatcher.AwaitableRunAsync(async () => + await dispatcherQueue.EnqueueAsync(async () => { image = new BitmapImage(); @@ -254,7 +256,7 @@ await dispatcher.AwaitableRunAsync(async () => return image; } - private static async Task LoadImageFromFile(string fileName, CoreDispatcher dispatcher) + private static async Task LoadImageFromFile(string fileName, DispatcherQueue dispatcherQueue) { try { @@ -264,7 +266,7 @@ private static async Task LoadImageFromFile(string fileName, Co var readStream = await file.OpenReadAsync(); WriteableBitmap image = null; - await dispatcher.AwaitableRunAsync(async () => image = await BitmapFactory.FromStream(readStream)); + await dispatcherQueue.EnqueueAsync(async () => image = await BitmapFactory.FromStream(readStream)); readStream.Dispose(); return image; @@ -275,12 +277,12 @@ private static async Task LoadImageFromFile(string fileName, Co } } - private async static Task ImageFromBytes(byte[] bytes, CoreDispatcher dispatcher) + private async static Task ImageFromBytes(byte[] bytes, DispatcherQueue dispatcherQueue) { WriteableBitmap image = null; using (var stream = new MemoryStream(bytes)) { - await dispatcher.AwaitableRunAsync(async () => + await dispatcherQueue.EnqueueAsync(async () => { image = await BitmapFactory.FromStream(stream); diff --git a/Sources/FluentMPC/Services/DialogService.cs b/Sources/FluentMPC/Services/DialogService.cs index ed2b2688..ee0835f9 100644 --- a/Sources/FluentMPC/Services/DialogService.cs +++ b/Sources/FluentMPC/Services/DialogService.cs @@ -2,10 +2,11 @@ using System.Threading.Tasks; using FluentMPC.Views; - +using Microsoft.Toolkit.Uwp; using Microsoft.Toolkit.Uwp.Helpers; using Windows.ApplicationModel.Core; +using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml.Controls; @@ -22,9 +23,7 @@ public static class DialogService public static async Task ShowAddToPlaylistDialog(bool allowExistingPlaylists = true) { var dialog = new AddToPlaylistDialog(allowExistingPlaylists); - var result = ContentDialogResult.None; - - await DispatcherHelper.ExecuteOnUIThreadAsync (async () => result = await dialog.ShowAsync()); + var result = await DispatcherService.DispatcherQueue.EnqueueAsync(async () => await dialog.ShowAsync()); // Return new playlist name if checked, selected playlist otherwise return result == ContentDialogResult.Primary ? dialog.AddNewPlaylist ? dialog.PlaylistName : dialog.SelectedPlaylist : null; @@ -36,7 +35,7 @@ internal static async Task ShowFirstRunDialogIfAppropriateAsync() await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, async () => { - if (SystemInformation.IsFirstRun && !shown) + if (SystemInformation.Instance.IsFirstRun && !shown) { shown = true; var dialog = new FirstRunDialog(); diff --git a/Sources/FluentMPC/Services/DispatcherService.cs b/Sources/FluentMPC/Services/DispatcherService.cs new file mode 100644 index 00000000..a016821b --- /dev/null +++ b/Sources/FluentMPC/Services/DispatcherService.cs @@ -0,0 +1,38 @@ +using Microsoft.Toolkit.Uwp; +using System; +using System.Threading.Tasks; +using Windows.System; + +namespace FluentMPC.Services +{ + public static class DispatcherService + { + private static DispatcherQueue _queue; + public static DispatcherQueue DispatcherQueue + { + get + { + if (_queue == null) + { + throw new Exception("The DispatcherQueue hasn't been cached yet!"); + } + + return _queue; + } + + set + { + _queue = value; + } + } + + public static void Initialize() + { + // Get a DispatcherQueue instance for later use. This has to be called on the UI thread, + // but it can then be cached for later use and accessed from a background thread as well. + DispatcherQueue = DispatcherQueue.GetForCurrentThread(); + } + + public static Task ExecuteOnUIThreadAsync(Action function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal) => DispatcherQueue.EnqueueAsync(function, priority); + } +} diff --git a/Sources/FluentMPC/Services/MPDConnectionService.cs b/Sources/FluentMPC/Services/MPDConnectionService.cs index 9d4684b5..e5116dea 100644 --- a/Sources/FluentMPC/Services/MPDConnectionService.cs +++ b/Sources/FluentMPC/Services/MPDConnectionService.cs @@ -134,16 +134,23 @@ public static async Task> GetConnectionAsync( /// /// Return type of the command /// IMpcCommand to send - /// CoreDispatcher, needed if you're executing commands in a state where the dispatcher can be a secondary one /// The command results, or default value. - public static async Task SafelySendCommandAsync(IMpcCommand command, CoreDispatcher dispatcher = null) + public static async Task SafelySendCommandAsync(IMpcCommand command) { try { using (var c = await GetConnectionAsync()) { var response = await c.InternalResource.SendAsync(command); - if (!response.IsResponseValid) throw new Exception($"Invalid server response: {response}."); + if (!response.IsResponseValid) + { + // If we have an MpdError string, only show that as the error to avoid extra noise + var mpdError = response.Response?.Result?.MpdError; + if (mpdError != null) + throw new Exception(mpdError); + else + throw new Exception($"Invalid server response: {response}."); + } return response.Response.Content; } @@ -152,7 +159,6 @@ public static async Task SafelySendCommandAsync(IMpcCommand command, Co { try { - if (dispatcher == null || dispatcher == CoreApplication.MainView.CoreWindow.Dispatcher) // Only invoke notificationservice on the main window NotificationService.ShowInAppNotification($"Sending {command.GetType().Name} failed: {e.Message}", 0); } catch { diff --git a/Sources/FluentMPC/Services/ThemeSelectorService.cs b/Sources/FluentMPC/Services/ThemeSelectorService.cs index a458a4c1..0bb38451 100644 --- a/Sources/FluentMPC/Services/ThemeSelectorService.cs +++ b/Sources/FluentMPC/Services/ThemeSelectorService.cs @@ -5,7 +5,9 @@ using Windows.ApplicationModel.Core; using Windows.Storage; +using Windows.UI; using Windows.UI.Core; +using Windows.UI.ViewManagement; using Windows.UI.Xaml; namespace FluentMPC.Services @@ -38,6 +40,30 @@ await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => if (Window.Current.Content is FrameworkElement frameworkElement) { frameworkElement.RequestedTheme = Theme; + + // https://stackoverflow.com/questions/48201278/uwp-changing-titlebar-buttonforegroundcolor-with-themeresource + Color color; + var appTheme = Application.Current.RequestedTheme; + + switch (Theme) + { + case ElementTheme.Default: + color = ((Color)Application.Current.Resources["SystemBaseHighColor"]); + break; + case ElementTheme.Light: + if (appTheme == ApplicationTheme.Light) { color = ((Color)Application.Current.Resources["SystemBaseHighColor"]); } + else { color = ((Color)Application.Current.Resources["SystemAltHighColor"]); } + break; + case ElementTheme.Dark: + if (appTheme == ApplicationTheme.Light) { color = ((Color)Application.Current.Resources["SystemAltHighColor"]); } + else { color = ((Color)Application.Current.Resources["SystemBaseHighColor"]); } + break; + default: + break; + } + + ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar; + titleBar.ButtonForegroundColor = color; } }); } diff --git a/Sources/FluentMPC/Styles/Page.xaml b/Sources/FluentMPC/Styles/Page.xaml index e2b3295c..87831a8a 100644 --- a/Sources/FluentMPC/Styles/Page.xaml +++ b/Sources/FluentMPC/Styles/Page.xaml @@ -4,11 +4,7 @@ xmlns:muxm="using:Microsoft.UI.Xaml.Media" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"> - - + @@ -17,6 +13,22 @@ BackgroundSource="HostBackdrop" TintColor="{StaticResource SystemChromeMediumColor}" TintOpacity="0.8" FallbackColor="{StaticResource SystemChromeMediumLowColor}" /> + + + + + + + + + + - - diff --git a/Sources/FluentMPC/Styles/_Colors.xaml b/Sources/FluentMPC/Styles/_Colors.xaml index caa4d4db..40d61b94 100644 --- a/Sources/FluentMPC/Styles/_Colors.xaml +++ b/Sources/FluentMPC/Styles/_Colors.xaml @@ -1,6 +1,6 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> diff --git a/Sources/FluentMPC/Styles/_FontSizes.xaml b/Sources/FluentMPC/Styles/_FontSizes.xaml index 7160b323..a19f4a3c 100644 --- a/Sources/FluentMPC/Styles/_FontSizes.xaml +++ b/Sources/FluentMPC/Styles/_FontSizes.xaml @@ -5,4 +5,11 @@ 24 16 + + + diff --git a/Sources/FluentMPC/ViewModels/AlbumDetailViewModel.cs b/Sources/FluentMPC/ViewModels/AlbumDetailViewModel.cs index 7679aff6..fcc855ba 100644 --- a/Sources/FluentMPC/ViewModels/AlbumDetailViewModel.cs +++ b/Sources/FluentMPC/ViewModels/AlbumDetailViewModel.cs @@ -8,12 +8,14 @@ using FluentMPC.Helpers; using FluentMPC.Services; using FluentMPC.ViewModels.Items; +using Microsoft.Toolkit.Uwp; using Microsoft.Toolkit.Uwp.Helpers; using MpcNET.Commands.Playback; using MpcNET.Commands.Playlist; using MpcNET.Commands.Queue; using MpcNET.Commands.Reflection; using MpcNET.Types; +using Windows.System; namespace FluentMPC.ViewModels { @@ -32,7 +34,7 @@ public string PlaylistInfo get => _info; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _info, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _info, value)); } } private string _info; @@ -94,7 +96,7 @@ public AlbumDetailViewModel() { } - public async Task InitializeAsync(AlbumViewModel album) + public void Initialize(AlbumViewModel album) { Item = album; @@ -110,7 +112,7 @@ public async Task InitializeAsync(AlbumViewModel album) album.PropertyChanged += async (s, e) => { if (album.Files.Count > 0 && Source.Count == 0) - await DispatcherHelper.ExecuteOnUIThreadAsync(() => CreateTrackViewModels()); + await DispatcherService.ExecuteOnUIThreadAsync(() => CreateTrackViewModels()); }; } else // AlbumVM hasn't been loaded at all, load it ourselves @@ -121,7 +123,7 @@ public async Task InitializeAsync(AlbumViewModel album) { await album.LoadAlbumDataAsync(c.InternalResource); } - await DispatcherHelper.ExecuteOnUIThreadAsync(() => CreateTrackViewModels()); + await DispatcherService.ExecuteOnUIThreadAsync(() => CreateTrackViewModels()); }); } diff --git a/Sources/FluentMPC/ViewModels/Items/AlbumViewModel.cs b/Sources/FluentMPC/ViewModels/Items/AlbumViewModel.cs index ed31470e..85674a7f 100644 --- a/Sources/FluentMPC/ViewModels/Items/AlbumViewModel.cs +++ b/Sources/FluentMPC/ViewModels/Items/AlbumViewModel.cs @@ -18,28 +18,29 @@ using System.Windows.Input; using Windows.System.Threading; using Windows.UI; +using Windows.UI.Xaml; using Windows.UI.Xaml.Media.Imaging; namespace FluentMPC.ViewModels.Items { public class AlbumViewModel : Observable { - public String Name + public string Name { get => _name; set => Set(ref _name, value); } - private String _name; + private string _name; - public String Artist + public string Artist { get => _artist; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _artist, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _artist, value)); } } - private String _artist; + private string _artist; public IList Files { @@ -54,7 +55,7 @@ public bool IsDetailLoading get => _detailLoading; internal set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _detailLoading, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _detailLoading, value)); } } @@ -64,7 +65,7 @@ public bool AlbumArtLoaded get => _artLoaded; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _artLoaded, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _artLoaded, value)); } } @@ -73,7 +74,7 @@ public BitmapImage AlbumArt get => _albumArt; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _albumArt, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _albumArt, value)); } } @@ -96,7 +97,7 @@ public Color DominantColor get => _albumColor; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _albumColor, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _albumColor, value)); } } @@ -111,7 +112,7 @@ public bool IsLight get => _isLight; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _isLight, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _isLight, value)); } } @@ -189,9 +190,11 @@ private async void PlayAlbum() public AlbumViewModel(string albumName) { Name = albumName; - DominantColor = Colors.Black; + DominantColor = (Color)Application.Current.Resources["SystemAccentColor"]; Files = new List(); IsDetailLoading = false; + + AlbumArt = new BitmapImage(new Uri("ms-appx:///Assets/AlbumPlaceholder.png")); } public async Task LoadAlbumDataAsync(MpcConnection c) diff --git a/Sources/FluentMPC/ViewModels/Items/FilePathViewModel.cs b/Sources/FluentMPC/ViewModels/Items/FilePathViewModel.cs index 30c98851..9eb58de5 100644 --- a/Sources/FluentMPC/ViewModels/Items/FilePathViewModel.cs +++ b/Sources/FluentMPC/ViewModels/Items/FilePathViewModel.cs @@ -1,23 +1,13 @@ -using ColorThiefDotNet; -using FluentMPC.Helpers; +using FluentMPC.Helpers; using FluentMPC.Services; -using Microsoft.Toolkit.Uwp.Helpers; -using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarSymbols; using MpcNET.Commands.Database; using MpcNET.Commands.Playback; using MpcNET.Commands.Playlist; using MpcNET.Types; using System; using System.Collections.Generic; -using System.Drawing; -using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using System.Windows.Input; -using Windows.Storage.Streams; -using Windows.UI.Core; -using Windows.UI.ViewManagement; -using Color = Windows.UI.Color; -using Windows.UI.Xaml.Media.Imaging; using System.Collections.ObjectModel; using Sundew.Base.Collections; using System.Linq; @@ -75,7 +65,7 @@ public async Task LoadChildrenAsync() else newChildren.Add(new FilePathViewModel("💥 Failed")); - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherService.ExecuteOnUIThreadAsync(() => { _childPaths.AddRange(newChildren); _childPaths.RemoveAt(0); // Remove the placeholder after adding the new items, otherwise the treeitem can close back up diff --git a/Sources/FluentMPC/ViewModels/Items/TrackViewModel.cs b/Sources/FluentMPC/ViewModels/Items/TrackViewModel.cs index b3198933..6b71c2b8 100644 --- a/Sources/FluentMPC/ViewModels/Items/TrackViewModel.cs +++ b/Sources/FluentMPC/ViewModels/Items/TrackViewModel.cs @@ -17,12 +17,14 @@ using MpcNET.Commands.Queue; using System.Threading; using FluentMPC.ViewModels.Playback; +using Windows.System; +using Microsoft.Toolkit.Uwp; namespace FluentMPC.ViewModels.Items { public class TrackViewModel : Observable { - private readonly CoreDispatcher _currentUiDispatcher; + private readonly DispatcherQueue _currentDispatcherQueue; public IMpdFile File { get; } @@ -30,14 +32,14 @@ public class TrackViewModel : Observable public bool IsPlaying => MPDConnectionService.CurrentStatus.SongId == File.Id; - internal void UpdatePlayingStatus() => DispatcherHelper.ExecuteOnUIThreadAsync(() => OnPropertyChanged(nameof(IsPlaying))); + internal void UpdatePlayingStatus() => DispatcherService.ExecuteOnUIThreadAsync(() => OnPropertyChanged(nameof(IsPlaying))); public BitmapImage AlbumArt { get => _albumArt; private set { - DispatcherHelper.AwaitableRunAsync(_currentUiDispatcher, () => Set(ref _albumArt, value)); + _currentDispatcherQueue.EnqueueAsync(() => Set(ref _albumArt, value)); } } @@ -48,7 +50,7 @@ public Color DominantColor get => _albumColor; private set { - DispatcherHelper.AwaitableRunAsync(_currentUiDispatcher, () => Set(ref _albumColor, value)); + _currentDispatcherQueue.EnqueueAsync(() => Set(ref _albumColor, value)); } } @@ -60,7 +62,7 @@ public bool IsLight get => _isLight; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _isLight, value)); + _currentDispatcherQueue.EnqueueAsync(() => Set(ref _isLight, value)); } } @@ -123,26 +125,35 @@ private void GoToMatchingAlbum(IMpdFile file) } } - public TrackViewModel(IMpdFile file, bool getAlbumArt = false, VisualizationType hostType = VisualizationType.None, CoreDispatcher dispatcher = null, CancellationToken albumArtCancellationToken = default) + public TrackViewModel(IMpdFile file, bool getAlbumArt = false, VisualizationType hostType = VisualizationType.None, DispatcherQueue dispatcherQueue = null, CancellationToken albumArtCancellationToken = default) { MPDConnectionService.SongChanged += (s, e) => UpdatePlayingStatus(); // Use specific UI dispatcher if given // (Used for the compact view scenario, which rolls its own dispatcher..) - _currentUiDispatcher = dispatcher ?? Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher; + _currentDispatcherQueue = dispatcherQueue ?? DispatcherService.DispatcherQueue; File = file; DominantColor = Colors.Black; // Fire off an async request to get the album art from MPD. if (getAlbumArt) + { + Task.Run(async () => { + await _currentDispatcherQueue.EnqueueAsync(() => + { + var placeholder = new BitmapImage(new Uri("ms-appx:///Assets/AlbumPlaceholder.png")); + placeholder.DecodePixelWidth = (int)hostType; + AlbumArt = placeholder; + }); + // This is RAM-intensive as it has to convert the image, so we only do it if needed (aka now playing bar and full playback only) var calculateDominantColor = hostType.IsOneOf(VisualizationType.NowPlayingBar, VisualizationType.FullScreenPlayback); // Use the int value of the VisualizationType to know how large the decoded bitmap has to be. - var art = await AlbumArtService.GetAlbumArtAsync(File, calculateDominantColor, (int)hostType, _currentUiDispatcher, albumArtCancellationToken); + var art = await AlbumArtService.GetAlbumArtAsync(File, calculateDominantColor, (int)hostType, _currentDispatcherQueue, albumArtCancellationToken); if (art != null) { @@ -155,6 +166,7 @@ public TrackViewModel(IMpdFile file, bool getAlbumArt = false, VisualizationType AlbumArt = art.ArtBitmap; } }); + } } } diff --git a/Sources/FluentMPC/ViewModels/Playback/PlaybackViewModel.cs b/Sources/FluentMPC/ViewModels/Playback/PlaybackViewModel.cs index d02f9e88..ac8e0f8f 100644 --- a/Sources/FluentMPC/ViewModels/Playback/PlaybackViewModel.cs +++ b/Sources/FluentMPC/ViewModels/Playback/PlaybackViewModel.cs @@ -2,6 +2,7 @@ using FluentMPC.Services; using FluentMPC.ViewModels.Items; using FluentMPC.Views; +using Microsoft.Toolkit.Uwp; using Microsoft.Toolkit.Uwp.Helpers; using MpcNET; using MpcNET.Commands.Playback; @@ -17,6 +18,7 @@ using Windows.ApplicationModel; using Windows.ApplicationModel.Core; using Windows.Foundation; +using Windows.System; using Windows.UI.Core; using Windows.UI.ViewManagement; using Windows.UI.Xaml; @@ -26,7 +28,7 @@ namespace FluentMPC.ViewModels.Playback { public class PlaybackViewModel : Observable { - private readonly CoreDispatcher _currentUiDispatcher; + private readonly DispatcherQueue _currentDispatcherQueue; private CancellationTokenSource _albumArtCancellationSource = new CancellationTokenSource(); @@ -69,17 +71,16 @@ public TrackViewModel NextTrack public VisualizationType HostType { get => _hostType; - set { + set + { Set(ref _hostType, value); - Task.Run(async () => - { - _albumArtCancellationSource.Cancel(); - _albumArtCancellationSource = new CancellationTokenSource(); - // Reload CurrentTrack to take into account the new VisualizationType - CurrentTrack = new TrackViewModel(CurrentTrack.File, true, HostType, _currentUiDispatcher, _albumArtCancellationSource.Token); - }); + _albumArtCancellationSource.Cancel(); + _albumArtCancellationSource = new CancellationTokenSource(); + + // Reload CurrentTrack to take into account the new VisualizationType + CurrentTrack = new TrackViewModel(CurrentTrack.File, true, HostType, _currentDispatcherQueue, _albumArtCancellationSource.Token); } } @@ -202,7 +203,7 @@ public double MediaVolume // Set the volume volumeTasks.Add(Task.Run(async () => { - await MPDConnectionService.SafelySendCommandAsync(new SetVolumeCommand((byte)value), _currentUiDispatcher); + await MPDConnectionService.SafelySendCommandAsync(new SetVolumeCommand((byte)value)); Thread.Sleep(1000); // Wait for MPD to acknowledge the new volume in its status... }, cts.Token)); @@ -300,12 +301,12 @@ public bool IsSingleEnabled #region Constructors - public PlaybackViewModel() : this(CoreApplication.MainView.Dispatcher, VisualizationType.None) + public PlaybackViewModel() : this(DispatcherService.DispatcherQueue, VisualizationType.None) { } - public PlaybackViewModel(CoreDispatcher uiDispatcher, VisualizationType hostType) + public PlaybackViewModel(DispatcherQueue uiDispatcher, VisualizationType hostType) { - _currentUiDispatcher = uiDispatcher; + _currentDispatcherQueue = uiDispatcher; _hostType = hostType; // Bind the methods that we need @@ -320,7 +321,7 @@ public PlaybackViewModel(CoreDispatcher uiDispatcher, VisualizationType hostType OnConnectionChanged(null, null); Application.Current.LeavingBackground += CurrentOnLeavingBackground; - NavigationService.Navigated += (s, e) => DispatcherHelper.AwaitableRunAsync(_currentUiDispatcher, () => OnPropertyChanged(nameof(ShowTrackName))); + NavigationService.Navigated += (s, e) => _currentDispatcherQueue.EnqueueAsync(() => OnPropertyChanged(nameof(ShowTrackName))); // Start the timer if ready if (!_updateInformationTimer.IsEnabled) @@ -339,7 +340,7 @@ private void OnConnectionChanged(object sender, EventArgs e) } } - private bool _isOnMainDispatcher => _currentUiDispatcher == CoreApplication.MainView.CoreWindow.Dispatcher; + private bool _isOnMainDispatcher => _currentDispatcherQueue == DispatcherService.DispatcherQueue; private void CurrentOnLeavingBackground(object sender, LeavingBackgroundEventArgs leavingBackgroundEventArgs) { @@ -366,7 +367,7 @@ private async void UpdateInformation(object sender, object e) if (!HasNextTrack) UpdateUpNextAsync(); - await _currentUiDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + await _currentDispatcherQueue.EnqueueAsync(() => { // Set the current time value - if the user isn't scrobbling the slider if (!_isUserMovingSlider) @@ -398,7 +399,7 @@ public async void SaveQueue() var playlistName = await DialogService.ShowAddToPlaylistDialog(false); if (playlistName == null) return; - var req = await MPDConnectionService.SafelySendCommandAsync(new SaveCommand(playlistName), _currentUiDispatcher); + var req = await MPDConnectionService.SafelySendCommandAsync(new SaveCommand(playlistName)); if (req != null) NotificationService.ShowInAppNotification(string.Format("AddedToPlaylistText".GetLocalized(), playlistName)); @@ -407,7 +408,7 @@ public async void SaveQueue() /// /// Clear the MPD queue. /// - public async void ClearQueue() => await MPDConnectionService.SafelySendCommandAsync(new ClearCommand(), _currentUiDispatcher); + public async void ClearQueue() => await MPDConnectionService.SafelySendCommandAsync(new ClearCommand()); /// /// Toggle if the current track/playlist should repeat @@ -438,8 +439,8 @@ public void ToggleRepeat() // Set the volume shuffleTasks.Add(Task.Run(async () => { - await MPDConnectionService.SafelySendCommandAsync(new RepeatCommand(IsRepeatEnabled), _currentUiDispatcher); - await MPDConnectionService.SafelySendCommandAsync(new SingleCommand(IsSingleEnabled), _currentUiDispatcher); + await MPDConnectionService.SafelySendCommandAsync(new RepeatCommand(IsRepeatEnabled)); + await MPDConnectionService.SafelySendCommandAsync(new SingleCommand(IsSingleEnabled)); Thread.Sleep(1000); // Wait for MPD to acknowledge the new status... }, cts.Token)); @@ -459,9 +460,9 @@ public void ToggleShuffle() // Set the volume shuffleTasks.Add(Task.Run(async () => { - await MPDConnectionService.SafelySendCommandAsync(new RandomCommand(IsShuffleEnabled), _currentUiDispatcher); + await MPDConnectionService.SafelySendCommandAsync(new RandomCommand(IsShuffleEnabled)); - await DispatcherHelper.AwaitableRunAsync(_currentUiDispatcher, UpdateUpNextAsync); + await _currentDispatcherQueue.EnqueueAsync(UpdateUpNextAsync); Thread.Sleep(1000); // Wait for MPD to acknowledge the new status... }, cts.Token)); } @@ -497,7 +498,7 @@ public void NavigateNowPlaying() /// Toggles the state between the track playing /// and not playing /// - public void ChangePlaybackState() => _ = MPDConnectionService.SafelySendCommandAsync(new PauseResumeCommand(), _currentUiDispatcher); + public void ChangePlaybackState() => _ = MPDConnectionService.SafelySendCommandAsync(new PauseResumeCommand()); /// /// Go forward one track @@ -506,7 +507,7 @@ public void SkipNext() { // Immediately cancel album art loading if it's in progress to free up MPD server resources _albumArtCancellationSource.Cancel(); - _ = MPDConnectionService.SafelySendCommandAsync(new NextCommand(), _currentUiDispatcher); + _ = MPDConnectionService.SafelySendCommandAsync(new NextCommand()); } /// @@ -516,7 +517,7 @@ public void SkipPrevious() { // Immediately cancel album art loading if it's in progress to free up MPD server resources _albumArtCancellationSource.Cancel(); - _ = MPDConnectionService.SafelySendCommandAsync(new PreviousCommand(), _currentUiDispatcher); + _ = MPDConnectionService.SafelySendCommandAsync(new PreviousCommand()); } #endregion Track Playback State @@ -540,7 +541,7 @@ public async void OnPlayingSliderChange() TimeRemaining = "-" + MiscHelpers.FormatTimeString(remainingTime.TotalMilliseconds); // Set the track position - await MPDConnectionService.SafelySendCommandAsync(new SeekCurCommand(CurrentTimeValue), _currentUiDispatcher); + await MPDConnectionService.SafelySendCommandAsync(new SeekCurCommand(CurrentTimeValue)); // Wait for MPD Status to catch up before we start auto-updating the slider again _ = Task.Run(() => @@ -554,11 +555,11 @@ private async void UpdateUpNextAsync() var nextSongId = MPDConnectionService.CurrentStatus.NextSongId; if (nextSongId != -1) { - var response = await MPDConnectionService.SafelySendCommandAsync(new PlaylistIdCommand(nextSongId), _currentUiDispatcher); + var response = await MPDConnectionService.SafelySendCommandAsync(new PlaylistIdCommand(nextSongId)); if (response != null) { - NextTrack = new TrackViewModel(response.First(), false, dispatcher:_currentUiDispatcher); + NextTrack = new TrackViewModel(response.First(), false, dispatcherQueue: _currentDispatcherQueue); } } } @@ -575,19 +576,19 @@ private async void OnTrackChange(object sender, SongChangedEventArgs eventArgs) return; // Get song info from MPD - var response = await MPDConnectionService.SafelySendCommandAsync(new CurrentSongCommand(), _currentUiDispatcher); + var response = await MPDConnectionService.SafelySendCommandAsync(new CurrentSongCommand()); // Cancel previous track art load _albumArtCancellationSource.Cancel(); _albumArtCancellationSource = new CancellationTokenSource(); // Run all this on the UI thread - await _currentUiDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + await _currentDispatcherQueue.EnqueueAsync(() => { if (response != null) { // Set the new current track, updating the UI with the correct Dispatcher - CurrentTrack = new TrackViewModel(response, true, HostType, _currentUiDispatcher, _albumArtCancellationSource.Token); + CurrentTrack = new TrackViewModel(response, true, HostType, _currentDispatcherQueue, _albumArtCancellationSource.Token); } else { @@ -606,7 +607,7 @@ await _currentUiDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => Singleton.Instance.UpdatePlayingSong(CurrentTrack); SystemMediaControlsService.UpdateMetadata(CurrentTrack); }); - + UpdateUpNextAsync(); } @@ -620,7 +621,7 @@ await _currentUiDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => private async void OnStateChange(object sender, EventArgs eventArgs) { - await _currentUiDispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + await _currentDispatcherQueue.EnqueueAsync(() => { // Remove completed requests volumeTasks.RemoveAll(t => t.IsCompleted); @@ -727,14 +728,14 @@ public async void SwitchToCompactView() var compactViewId = -1; var currentViewId = -1; - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherService.ExecuteOnUIThreadAsync(() => { // Get the Id back currentViewId = ApplicationView.GetForCurrentView().Id; }); // Create a new window within the view - await DispatcherHelper.ExecuteOnUIThreadAsync(compactView, () => + await compactView.DispatcherQueue.EnqueueAsync(() => { // Create a new frame and navigate it to the overlay view var overlayFrame = new Frame(); diff --git a/Sources/FluentMPC/ViewModels/PlaylistViewModel.cs b/Sources/FluentMPC/ViewModels/PlaylistViewModel.cs index e60beb64..f95b40e7 100644 --- a/Sources/FluentMPC/ViewModels/PlaylistViewModel.cs +++ b/Sources/FluentMPC/ViewModels/PlaylistViewModel.cs @@ -50,6 +50,7 @@ private async void DeletePlaylist() PrimaryButtonText = "OKButtonText".GetLocalized(), CloseButtonText = "CancelButtonText".GetLocalized() }; + confirmDialog.RequestedTheme = ThemeSelectorService.Theme; ContentDialogResult result = await confirmDialog.ShowAsync(); if (result != ContentDialogResult.Primary) @@ -157,6 +158,10 @@ private async void RemoveTrack(object list) public PlaylistViewModel() { Source.CollectionChanged += (s, e) => OnPropertyChanged(nameof(IsSourceEmpty)); + + PlaylistArt = new BitmapImage(new Uri("ms-appx:///Assets/AlbumPlaceholder.png")); + PlaylistArt2 = new BitmapImage(new Uri("ms-appx:///Assets/AlbumPlaceholder.png")); + PlaylistArt3 = new BitmapImage(new Uri("ms-appx:///Assets/AlbumPlaceholder.png")); } public string Name @@ -171,7 +176,7 @@ public string Artists get => _artists; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _artists, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _artists, value)); } } private string _artists; @@ -181,7 +186,7 @@ public string PlaylistInfo get => _info; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _info, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _info, value)); } } private string _info; @@ -198,7 +203,7 @@ public BitmapImage PlaylistArt get => _playlistArt; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _playlistArt, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _playlistArt, value)); } } @@ -209,7 +214,7 @@ public BitmapImage PlaylistArt2 get => _playlistArt2; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _playlistArt2, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _playlistArt2, value)); } } @@ -220,7 +225,7 @@ public BitmapImage PlaylistArt3 get => _playlistArt3; private set { - DispatcherHelper.ExecuteOnUIThreadAsync(() => Set(ref _playlistArt3, value)); + DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _playlistArt3, value)); } } @@ -278,7 +283,7 @@ await Task.Run(async () => } else PlaylistArt3 = PlaylistArt2; - await DispatcherHelper.ExecuteOnUIThreadAsync(() => ArtLoaded = true); + await DispatcherService.ExecuteOnUIThreadAsync(() => ArtLoaded = true); }); } diff --git a/Sources/FluentMPC/ViewModels/QueueViewModel.cs b/Sources/FluentMPC/ViewModels/QueueViewModel.cs index 21d0de46..abc9407a 100644 --- a/Sources/FluentMPC/ViewModels/QueueViewModel.cs +++ b/Sources/FluentMPC/ViewModels/QueueViewModel.cs @@ -165,7 +165,7 @@ private async void MPDConnectionService_QueueChanged(object sender, EventArgs e) if (response != null) { Source.CollectionChanged -= Source_CollectionChanged; - await DispatcherHelper.ExecuteOnUIThreadAsync(() => { + await DispatcherService.ExecuteOnUIThreadAsync(() => { // If the queue was cleared, PlaylistLength is 0. if (status.PlaylistLength == 0) @@ -215,7 +215,7 @@ public async Task LoadInitialDataAsync() tracks.Add(new TrackViewModel(item, false)); } - await DispatcherHelper.ExecuteOnUIThreadAsync(() => { + await DispatcherService.ExecuteOnUIThreadAsync(() => { Source.Clear(); Source.AddRange(tracks); }); diff --git a/Sources/FluentMPC/ViewModels/SearchResultsViewModel.cs b/Sources/FluentMPC/ViewModels/SearchResultsViewModel.cs index 76d6c01f..7a733c72 100644 --- a/Sources/FluentMPC/ViewModels/SearchResultsViewModel.cs +++ b/Sources/FluentMPC/ViewModels/SearchResultsViewModel.cs @@ -8,6 +8,7 @@ using FluentMPC.Helpers; using FluentMPC.Services; using FluentMPC.ViewModels.Items; +using Microsoft.Toolkit.Uwp; using Microsoft.Toolkit.Uwp.Helpers; using MpcNET.Commands.Database; using MpcNET.Commands.Playback; @@ -16,6 +17,7 @@ using MpcNET.Commands.Reflection; using MpcNET.Tags; using MpcNET.Types; +using Windows.System; namespace FluentMPC.ViewModels { @@ -180,13 +182,13 @@ public SearchResultsViewModel() _searchTracks = true; } - public async Task InitializeAsync(string searchQuery) + public void Initialize(string searchQuery) { QueryText = searchQuery; UpdateSource(); } - private async void UpdateSource() + private void UpdateSource() { IsSearchInProgress = true; Source.Clear(); @@ -197,7 +199,7 @@ private async void UpdateSource() if (SearchAlbums) await DoSearchAsync(FindTags.Album); if (SearchArtists) await DoSearchAsync(FindTags.Artist); - await DispatcherHelper.ExecuteOnUIThreadAsync(() => IsSearchInProgress = false); + await DispatcherService.ExecuteOnUIThreadAsync(() => IsSearchInProgress = false); }); } @@ -207,7 +209,7 @@ private async Task DoSearchAsync(ITag tag) if (response != null) { - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherService.ExecuteOnUIThreadAsync(() => { foreach (var f in response) { diff --git a/Sources/FluentMPC/ViewModels/SettingsViewModel.cs b/Sources/FluentMPC/ViewModels/SettingsViewModel.cs index 58196741..bd2106d5 100644 --- a/Sources/FluentMPC/ViewModels/SettingsViewModel.cs +++ b/Sources/FluentMPC/ViewModels/SettingsViewModel.cs @@ -212,6 +212,7 @@ public ICommand RescanDbCommand PrimaryButtonText = "OKButtonText".GetLocalized(), CloseButtonText = "CancelButtonText".GetLocalized() }; + confirmDialog.RequestedTheme = ThemeSelectorService.Theme; ContentDialogResult result = await confirmDialog.ShowAsync(); if (result != ContentDialogResult.Primary) @@ -286,10 +287,10 @@ private async Task CheckServerAddressAsync() { if (IsCheckingServer) return; - await DispatcherHelper.ExecuteOnUIThreadAsync(() => IsCheckingServer = true); + await DispatcherService.ExecuteOnUIThreadAsync(() => IsCheckingServer = true); await MPDConnectionService.InitializeAsync(); - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherService.ExecuteOnUIThreadAsync(() => { OnPropertyChanged(nameof(IsServerValid)); IsCheckingServer = false; @@ -312,7 +313,7 @@ private async Task UpdateServerVersionAsync() } // Build info string - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherService.ExecuteOnUIThreadAsync(() => ServerInfo = $"MPD Protocol {MPDConnectionService.Version}\n" + $"{response["songs"]} Songs, {response["albums"]} Albums\n" + $"Database last updated {lastUpdatedDb}"); diff --git a/Sources/FluentMPC/ViewModels/ShellViewModel.cs b/Sources/FluentMPC/ViewModels/ShellViewModel.cs index 9c1e89a6..2ec138ad 100644 --- a/Sources/FluentMPC/ViewModels/ShellViewModel.cs +++ b/Sources/FluentMPC/ViewModels/ShellViewModel.cs @@ -81,14 +81,14 @@ public void Initialize(Frame frame, WinUI.NavigationView navigationView, WinUI.N NotificationService.InAppNotificationRequested += Show_InAppNotification; - DispatcherHelper.ExecuteOnUIThreadAsync(() => UpdatePlaylistNavigation()); + DispatcherService.ExecuteOnUIThreadAsync(() => UpdatePlaylistNavigation()); MPDConnectionService.PlaylistsChanged += (s,e) => - DispatcherHelper.ExecuteOnUIThreadAsync(() => UpdatePlaylistNavigation()); + DispatcherService.ExecuteOnUIThreadAsync(() => UpdatePlaylistNavigation()); } private void Show_InAppNotification(object sender, InAppNotificationRequestedEventArgs e) { - _notificationHolder.Show(e.NotificationText, e.NotificationTime); + DispatcherService.ExecuteOnUIThreadAsync(() => _notificationHolder.Show(e.NotificationText, e.NotificationTime)); } private async void OnLoaded() diff --git a/Sources/FluentMPC/Views/LibraryDetailPage.xaml b/Sources/FluentMPC/Views/LibraryDetailPage.xaml index 14c63801..f255ccee 100644 --- a/Sources/FluentMPC/Views/LibraryDetailPage.xaml +++ b/Sources/FluentMPC/Views/LibraryDetailPage.xaml @@ -4,12 +4,15 @@ 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" - xmlns:winui="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:winui="using:Microsoft.UI.Xaml.Controls" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters" - xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI.Extensions" + xmlns:ui="using:Microsoft.Toolkit.Uwp.UI" xmlns:fluentMpc="using:FluentMPC.Helpers" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" - xmlns:behaviors="using:FluentMPC.Behaviors" xmlns:items="using:FluentMPC.ViewModels.Items" + xmlns:interactivity="using:Microsoft.Xaml.Interactivity" + xmlns:behaviors="using:FluentMPC.Behaviors" + xmlns:toolkitbehaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors" + xmlns:items="using:FluentMPC.ViewModels.Items" Style="{StaticResource PageStyle}" behaviors:NavigationViewHeaderBehavior.HeaderMode="Minimal" mc:Ignorable="d"> @@ -81,7 +84,7 @@ - + @@ -96,8 +99,8 @@ Margin="0,-96" DoubleTapped="Queue_Track" RightTapped="Select_Item" - extensions:ScrollViewerExtensions.EnableMiddleClickScrolling="True" - extensions:ScrollViewerExtensions.VerticalScrollBarMargin="{Binding ActualHeight, ElementName=Header, Converter={StaticResource DoubleTopThicknessConverter}}" + ui:ScrollViewerExtensions.EnableMiddleClickScrolling="True" + ui:ScrollViewerExtensions.VerticalScrollBarMargin="{Binding ActualHeight, ElementName=Header, Converter={StaticResource DoubleTopThicknessConverter}}" OddRowBackground="{ThemeResource SystemControlPageBackgroundListLowBrush}" EvenRowBackground="{ThemeResource ApplicationPageBackgroundThemeBrush}" ItemTemplate="{StaticResource TrackListViewTemplate}" @@ -125,26 +128,27 @@ - + + + + - - - + - - - + - - + + - - - - - + + + + - - - - - + + + + - - - - - + + + + - - - - - - - - + + + + + + - - - - - + + + + + + + + diff --git a/Sources/FluentMPC/Views/LibraryDetailPage.xaml.cs b/Sources/FluentMPC/Views/LibraryDetailPage.xaml.cs index 44299457..fc61062e 100644 --- a/Sources/FluentMPC/Views/LibraryDetailPage.xaml.cs +++ b/Sources/FluentMPC/Views/LibraryDetailPage.xaml.cs @@ -7,6 +7,8 @@ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; +using Windows.UI.Xaml.Media; +using System.Numerics; namespace FluentMPC.Views { @@ -19,13 +21,13 @@ public LibraryDetailPage() InitializeComponent(); } - protected override async void OnNavigatedTo(NavigationEventArgs e) + protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); this.RegisterElementForConnectedAnimation("animationKeyLibrary", itemHero); if (e.Parameter is AlbumViewModel album) { - await ViewModel.InitializeAsync(album); + ViewModel.Initialize(album); } } diff --git a/Sources/FluentMPC/Views/LibraryPage.xaml b/Sources/FluentMPC/Views/LibraryPage.xaml index 2b018349..d3e32a55 100644 --- a/Sources/FluentMPC/Views/LibraryPage.xaml +++ b/Sources/FluentMPC/Views/LibraryPage.xaml @@ -9,7 +9,7 @@ xmlns:winui="using:Microsoft.UI.Xaml.Controls" xmlns:items="using:FluentMPC.ViewModels.Items" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters" - xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI.Extensions" + xmlns:ui="using:Microsoft.Toolkit.Uwp.UI" Style="{StaticResource PageStyle}" NavigationCacheMode="Required" mc:Ignorable="d"> @@ -29,7 +29,7 @@ animations:Connected.ListItemKey="animationKeyLibrary" IsItemClickEnabled="True" ItemClick="AlbumClicked" - extensions:ScrollViewerExtensions.EnableMiddleClickScrolling="True" + ui:ScrollViewerExtensions.EnableMiddleClickScrolling="True" ItemsSource="{x:Bind ViewModel.FilteredSource,Mode=OneWay}" SelectionMode="None"> @@ -63,9 +63,7 @@ - + diff --git a/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml b/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml index 313499ef..e188b43b 100644 --- a/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml +++ b/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml @@ -38,7 +38,7 @@ - - + diff --git a/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml.cs b/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml.cs index cdcccb26..b07eca5b 100644 --- a/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml.cs +++ b/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml.cs @@ -13,12 +13,14 @@ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media.Animation; using FluentMPC.Services; +using Microsoft.Toolkit.Uwp; +using Windows.System; namespace FluentMPC.Views { public sealed partial class NowPlayingBar { - public PlaybackViewModel PlaybackViewModel { get; } = new PlaybackViewModel(CoreWindow.GetForCurrentThread().Dispatcher, VisualizationType.NowPlayingBar); + public PlaybackViewModel PlaybackViewModel { get; } = new PlaybackViewModel(DispatcherQueue.GetForCurrentThread(), VisualizationType.NowPlayingBar); public NowPlayingBar() => InitializeComponent(); @@ -45,7 +47,7 @@ private void OnUnloaded(object sender, RoutedEventArgs e) private void UpdateBars() { - DispatcherHelper.ExecuteOnUIThreadAsync(() => + DispatcherService.DispatcherQueue.EnqueueAsync(() => { if (!MPDConnectionService.IsConnected) { @@ -70,7 +72,7 @@ private void PlaybackSession_PlaybackStateChanged(object sender, EventArgs event private async Task PlaybackSession_PlaybackStateChangedAsync(object sender, EventArgs eventArgs) { - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherService.DispatcherQueue.EnqueueAsync(() => { switch (MPDConnectionService.CurrentStatus.State) { diff --git a/Sources/FluentMPC/Views/Playback/OverlayView.xaml b/Sources/FluentMPC/Views/Playback/OverlayView.xaml index 5f63fa6f..2bd0d835 100644 --- a/Sources/FluentMPC/Views/Playback/OverlayView.xaml +++ b/Sources/FluentMPC/Views/Playback/OverlayView.xaml @@ -71,8 +71,7 @@ - diff --git a/Sources/FluentMPC/Views/Playback/OverlayView.xaml.cs b/Sources/FluentMPC/Views/Playback/OverlayView.xaml.cs index 412cf53b..520a93ef 100644 --- a/Sources/FluentMPC/Views/Playback/OverlayView.xaml.cs +++ b/Sources/FluentMPC/Views/Playback/OverlayView.xaml.cs @@ -1,9 +1,11 @@ using FluentMPC.Services; using FluentMPC.ViewModels.Playback; +using Microsoft.Toolkit.Uwp; using Microsoft.Toolkit.Uwp.Helpers; using System; using System.Threading.Tasks; using Windows.ApplicationModel.Core; +using Windows.System; using Windows.UI; using Windows.UI.Core; using Windows.UI.ViewManagement; @@ -26,7 +28,7 @@ public OverlayView() protected override void OnNavigatedTo(NavigationEventArgs e) { - PlaybackViewModel = new PlaybackViewModel(CoreWindow.GetForCurrentThread().Dispatcher, VisualizationType.OverlayPlayback); + PlaybackViewModel = new PlaybackViewModel(DispatcherQueue.GetForCurrentThread(), VisualizationType.OverlayPlayback); _mainAppViewId = (int)e.Parameter; CoreApplicationViewTitleBar coreTitleBar = CoreApplication.GetCurrentView().TitleBar; @@ -38,7 +40,7 @@ private async void NavigateToMainView() { var currentViewId = -1; - await DispatcherHelper.AwaitableRunAsync(CoreWindow.GetForCurrentThread().Dispatcher,() => + await DispatcherQueue.GetForCurrentThread().EnqueueAsync(() => { currentViewId = ApplicationView.GetForCurrentView().Id; }); diff --git a/Sources/FluentMPC/Views/Playback/PlaybackView.xaml b/Sources/FluentMPC/Views/Playback/PlaybackView.xaml index 12a27fd4..bc5715d0 100644 --- a/Sources/FluentMPC/Views/Playback/PlaybackView.xaml +++ b/Sources/FluentMPC/Views/Playback/PlaybackView.xaml @@ -34,9 +34,9 @@ RelativePanel.AlignRightWithPanel="True" Canvas.ZIndex="-1" Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"> - - - + @@ -94,6 +94,7 @@ Opacity="0.6" MaxLines="3" TextWrapping="Wrap" + FontWeight="SemiLight" TextTrimming="CharacterEllipsis" /> diff --git a/Sources/FluentMPC/Views/PlaylistPage.xaml b/Sources/FluentMPC/Views/PlaylistPage.xaml index 3deb58b1..2ec15b82 100644 --- a/Sources/FluentMPC/Views/PlaylistPage.xaml +++ b/Sources/FluentMPC/Views/PlaylistPage.xaml @@ -4,12 +4,14 @@ 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" - xmlns:winui="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters" - xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI.Extensions" + xmlns:ui="using:Microsoft.Toolkit.Uwp.UI" xmlns:fluentMpc="using:FluentMPC.Helpers" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" - xmlns:behaviors="using:FluentMPC.Behaviors" xmlns:items="using:FluentMPC.ViewModels.Items" + xmlns:interactivity="using:Microsoft.Xaml.Interactivity" + xmlns:behaviors="using:FluentMPC.Behaviors" + xmlns:toolkitbehaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors" + xmlns:items="using:FluentMPC.ViewModels.Items" Style="{StaticResource PageStyle}" behaviors:NavigationViewHeaderBehavior.HeaderMode="Minimal" mc:Ignorable="d"> @@ -83,7 +85,7 @@ - + @@ -101,8 +103,8 @@ ReorderMode="Enabled" DoubleTapped="Queue_Track" RightTapped="Select_Item" - extensions:ScrollViewerExtensions.VerticalScrollBarMargin="{Binding ActualHeight, ElementName=Header, Converter={StaticResource DoubleTopThicknessConverter}}" - extensions:ScrollViewerExtensions.EnableMiddleClickScrolling="True" + ui:ScrollViewerExtensions.VerticalScrollBarMargin="{Binding ActualHeight, ElementName=Header, Converter={StaticResource DoubleTopThicknessConverter}}" + ui:ScrollViewerExtensions.EnableMiddleClickScrolling="True" OddRowBackground="{ThemeResource SystemControlPageBackgroundListLowBrush}" EvenRowBackground="{ThemeResource ApplicationPageBackgroundThemeBrush}" ItemTemplate="{StaticResource TrackListViewTemplate}" @@ -144,36 +146,37 @@ + + + - - - + - - - + - - + + - - - - - + + + + - - - - + + + - + - - - - + + + - + - - - - + + + - + - - - - - - + + - - + + - - + diff --git a/Sources/FluentMPC/Views/SearchResultsPage.xaml b/Sources/FluentMPC/Views/SearchResultsPage.xaml index abb7d72e..5e975ebf 100644 --- a/Sources/FluentMPC/Views/SearchResultsPage.xaml +++ b/Sources/FluentMPC/Views/SearchResultsPage.xaml @@ -4,12 +4,11 @@ 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" - xmlns:winui="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:winui="using:Microsoft.UI.Xaml.Controls" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters" - xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI.Extensions" + xmlns:ui="using:Microsoft.Toolkit.Uwp.UI" xmlns:fluentMpc="using:FluentMPC.Helpers" - xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" - xmlns:behaviors="using:FluentMPC.Behaviors" xmlns:items="using:FluentMPC.ViewModels.Items" + xmlns:items="using:FluentMPC.ViewModels.Items" Style="{StaticResource PageStyle}" mc:Ignorable="d"> @@ -74,9 +73,9 @@ - - - + + + t.File.Id == e.NewSongId && t.File.Id != manualSongId).FirstOrDefault(); if (playing != null) - DispatcherHelper.ExecuteOnUIThreadAsync(() => QueueList.ScrollIntoView(playing, ScrollIntoViewAlignment.Leading)); + DispatcherService.ExecuteOnUIThreadAsync(() => QueueList.ScrollIntoView(playing, ScrollIntoViewAlignment.Leading)); } private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) diff --git a/Sources/FluentMPC/Views/SettingsPage.xaml b/Sources/FluentMPC/Views/SettingsPage.xaml index 7e1018cb..a9940895 100644 --- a/Sources/FluentMPC/Views/SettingsPage.xaml +++ b/Sources/FluentMPC/Views/SettingsPage.xaml @@ -26,7 +26,7 @@ + Value="{x:Bind ViewModel.ServerPort, Mode=TwoWay}" Minimum="1" Maximum="65536"/> diff --git a/Sources/FluentMPC/Views/ShellPage.xaml b/Sources/FluentMPC/Views/ShellPage.xaml index 07dee878..87a3f24f 100644 --- a/Sources/FluentMPC/Views/ShellPage.xaml +++ b/Sources/FluentMPC/Views/ShellPage.xaml @@ -59,6 +59,7 @@ SelectedItem="{x:Bind ViewModel.Selected, Mode=OneWay}" IsSettingsVisible="True" ExpandedModeThresholdWidth="900" + Loaded="ApplyShadowToSideBar" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> @@ -82,12 +83,27 @@ DefaultHeader="{x:Bind ViewModel.Selected.Content, Mode=OneWay}"> - - + + + + + + + + + + + + + + + + + + + + @@ -96,12 +112,20 @@ - + + + + + - - + + + + + + diff --git a/Sources/FluentMPC/Views/ShellPage.xaml.cs b/Sources/FluentMPC/Views/ShellPage.xaml.cs index b5d86915..1b1e35a3 100644 --- a/Sources/FluentMPC/Views/ShellPage.xaml.cs +++ b/Sources/FluentMPC/Views/ShellPage.xaml.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Numerics; +using FluentMPC.Services; using FluentMPC.ViewModels; using MpcNET.Types; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; namespace FluentMPC.Views { @@ -27,6 +30,9 @@ public sealed partial class ShellPage : Page public ShellPage() { + // First View, use that to initialize our cached DispatcherQueue + DispatcherService.Initialize(); + InitializeComponent(); DataContext = ViewModel; ViewModel.Initialize(shellFrame, navigationView, playlistContainer, notificationHolder, KeyboardAccelerators); @@ -40,5 +46,43 @@ private void OpenSuggestionsPanel(object sender, RoutedEventArgs args) box.IsSuggestionListOpen = true; } + private void ApplyShadowToSideBar(object sender, RoutedEventArgs e) + { + // Some VisualTree hacking to get the content grid for left display mode and cast shadows over it + // https://github.com/microsoft/microsoft-ui-xaml/blob/master/dev/NavigationView/docs/rendering.md#displaymode-left + Grid rootGrid = VisualTreeHelper.GetChild(navigationView, 0) as Grid; + if (rootGrid != null) + { + // Get the pane's grid, which receives all our shadows + var paneContentGrid = rootGrid.FindName("PaneContentGrid") as Grid; + + // Shadow emitters for the header. The header has grids for both its content and top padding, so we need to shadow up both of them. + var headerContent = rootGrid.FindName("HeaderContent") as ContentControl; + var headerTopContent = rootGrid.FindName("ContentTopPadding") as Grid; + var headerShadow = new ThemeShadow(); + var headerTopShadow = new ThemeShadow(); + + // Remove default HeaderContent margin so the shadow can be cast correctly. + // You can set NavigationViewHeaderMargin again in your own content to match. + headerContent.Margin = new Thickness(0); + + // Set receivers + headerShadow.Receivers.Add(paneContentGrid); + headerTopShadow.Receivers.Add(paneContentGrid); + ContentShadow.Receivers.Add(paneContentGrid); + + //NowPlayingShadow.Receivers.Add(paneContentGrid); + NowPlayingShadow.Receivers.Add(navigationView); + + headerContent.Shadow = headerShadow; + headerContent.Translation += new Vector3(0, 0, 32); + + headerTopContent.Shadow = headerTopShadow; + headerTopContent.Translation += new Vector3(0, 0, 32); + + shellFrame.Translation += new Vector3(0, 0, 32); + nowPlayingBar.Translation += new Vector3(0, 0, 64); + } + } } }