diff --git a/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs b/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs index 3f8cf29..6825b22 100644 --- a/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs +++ b/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs @@ -12,8 +12,6 @@ namespace Harmonia.WinUI.Caching; public class AudioBitmapImageCache(IAudioImageExtractor audioImageExtractor) : MemoryCache, IAudioBitmapImageCache { - protected virtual int MaxImageWidthOrHeight => 1000; - protected override MemoryCacheOptions Options => new() { SizeLimit = 200_000_000, @@ -21,8 +19,8 @@ public class AudioBitmapImageCache(IAudioImageExtractor audioImageExtractor) : M }; protected override TimeSpan SlidingExpiration => TimeSpan.FromSeconds(600); - protected override int MaxConcurrentRequests => 8; + protected virtual int MaxImageWidthOrHeight => 1000; protected override object? GetKey(Song key) { @@ -35,7 +33,7 @@ public class AudioBitmapImageCache(IAudioImageExtractor audioImageExtractor) : M return key.ImageName; } - return null; + return "Default"; } protected override async ValueTask FetchAsync(Song key, CancellationToken cancellationToken) @@ -43,7 +41,7 @@ public class AudioBitmapImageCache(IAudioImageExtractor audioImageExtractor) : M SongPictureInfo? songPictureInfo = await audioImageExtractor.ExtractImageAsync(key.FileName, cancellationToken); if (songPictureInfo == null) - return null; + return GetDefaultBitmapImage(); using MemoryStream stream = new(songPictureInfo.Data); @@ -57,6 +55,18 @@ public class AudioBitmapImageCache(IAudioImageExtractor audioImageExtractor) : M return bitmapImage; } + private BitmapImage GetDefaultBitmapImage() + { + Uri uri = new("ms-appx:///Assets/Default.png", UriKind.Absolute); + + BitmapImage bitmapImage = new(); + bitmapImage.DecodePixelWidth = GetDecodePixelWidth(bitmapImage); + bitmapImage.DecodePixelHeight = GetDecodePixelHeight(bitmapImage); + bitmapImage.UriSource = uri; + + return bitmapImage; + } + private int GetDecodePixelWidth(BitmapImage bitmapImage) { int originalImageWidth = bitmapImage.PixelWidth; diff --git a/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs b/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs index 17afc57..5540450 100644 --- a/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs +++ b/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs @@ -1,5 +1,6 @@ using Microsoft.UI.Xaml.Data; using System; +using System.Collections.Generic; namespace Harmonia.WinUI.Converters; @@ -12,7 +13,7 @@ public partial class ArtistsToStringConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, string language) { - if (value is not string[] artists) + if (value is not ICollection artists) return string.Empty; return string.Join(" / ", artists); diff --git a/Harmonia.WinUI/Converters/NullVisibilityConverter.cs b/Harmonia.WinUI/Converters/NullVisibilityConverter.cs index 9b76c2e..59e6558 100644 --- a/Harmonia.WinUI/Converters/NullVisibilityConverter.cs +++ b/Harmonia.WinUI/Converters/NullVisibilityConverter.cs @@ -15,7 +15,7 @@ public sealed partial class NullVisibilityConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, string language) { if (value is not IList list) - return Visibility.Collapsed; + return value == null ? Visibility.Collapsed : Visibility.Visible; return list.Count == 0 ? Visibility.Collapsed : Visibility.Visible; } diff --git a/Harmonia.WinUI/Harmonia.WinUI.csproj b/Harmonia.WinUI/Harmonia.WinUI.csproj index 0d5320f..8a79786 100644 --- a/Harmonia.WinUI/Harmonia.WinUI.csproj +++ b/Harmonia.WinUI/Harmonia.WinUI.csproj @@ -50,6 +50,11 @@ + + + PreserveNewest + + Designer diff --git a/Harmonia.WinUI/Resources/Geometry.xaml b/Harmonia.WinUI/Resources/Geometry.xaml index 0a0ff2d..05f029c 100644 --- a/Harmonia.WinUI/Resources/Geometry.xaml +++ b/Harmonia.WinUI/Resources/Geometry.xaml @@ -94,7 +94,7 @@ M3.5 3.5c-.614-.884-.074-1.962.858-2.5L8 7.226 11.642 1c.932.538 1.472 1.616.858 2.5L8.81 8.61l1.556 2.661a2.5 2.5 0 1 1-.794.637L8 9.73l-1.572 2.177a2.5 2.5 0 1 1-.794-.637L7.19 8.61zm2.5 10a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0m7 0a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0 - + M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z diff --git a/Harmonia.WinUI/Resources/Styles.xaml b/Harmonia.WinUI/Resources/Styles.xaml index ad73a73..bfd6895 100644 --- a/Harmonia.WinUI/Resources/Styles.xaml +++ b/Harmonia.WinUI/Resources/Styles.xaml @@ -2,7 +2,24 @@ - + + + + + + + + + + + - + @@ -43,15 +52,16 @@ - - + + + + + - - + + + + + + - + @@ -80,10 +93,10 @@ - - - - + + + + diff --git a/Harmonia.WinUI/Views/PlaylistView.xaml b/Harmonia.WinUI/Views/PlaylistView.xaml index 05c411e..0d3e134 100644 --- a/Harmonia.WinUI/Views/PlaylistView.xaml +++ b/Harmonia.WinUI/Views/PlaylistView.xaml @@ -26,14 +26,14 @@ - + @@ -55,11 +55,11 @@ - + - + - + @@ -128,7 +128,66 @@ Name="PlaylistListView" ItemsSource="{Binding FilteredPlaylistSongs}" ItemTemplate="{StaticResource SongTemplate}" - SelectionMode="Extended"> + SelectionMode="Extended" + SelectionChanged="PlaylistListView_SelectionChanged"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Harmonia.WinUI/Views/PlaylistView.xaml.cs b/Harmonia.WinUI/Views/PlaylistView.xaml.cs index 58d5c2d..8855c1b 100644 --- a/Harmonia.WinUI/Views/PlaylistView.xaml.cs +++ b/Harmonia.WinUI/Views/PlaylistView.xaml.cs @@ -1,9 +1,14 @@ -using Harmonia.Core.Playlists; +using CommunityToolkit.WinUI; +using Harmonia.Core.Playlists; using Harmonia.WinUI.ViewModels; +using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Input; -using System.Threading.Tasks; +using Microsoft.UI.Xaml.Media.Imaging; +using System; +using System.Linq; namespace Harmonia.WinUI.Views; @@ -14,34 +19,97 @@ public sealed partial class PlaylistView : UserControl public PlaylistView() { InitializeComponent(); + _viewModel = (PlaylistViewModel)DataContext; + _viewModel.PlayingSongChangedAutomatically += OnPlayingSongChangedAutomatically; + + foreach (MenuFlyoutItemBase item in PlaylistListViewMenuFlyout.Items) + { + item.DataContextChanged += Item_DataContextChanged; + } + } + + private void OnPlayingSongChangedAutomatically(object? sender, EventArgs e) + { + BringPlayingSongIntoView(); + } + + private void BringPlayingSongIntoView() + { + PlaylistSong? playingSong = _viewModel.PlayingSong; + + if (playingSong == null) + return; + + ListViewItem listViewItem = (ListViewItem)PlaylistListView.ContainerFromItem(playingSong); + + if (listViewItem != null) + { + listViewItem.UpdateLayout(); + listViewItem.StartBringIntoView(new BringIntoViewOptions() { VerticalAlignmentRatio = .5, AnimationDesired = true }); + } + else + { + PlaylistListView.UpdateLayout(); + PlaylistListView.ScrollIntoView(playingSong); + } + } + + private void Item_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + if (sender is not MenuFlyoutItemBase item) + return; + + if (args.NewValue == _viewModel) + return; + + item.DataContext = _viewModel; } private void Image_Loaded(object sender, RoutedEventArgs e) { - //Image? image = sender as Image; + if (sender is not Image image) + return; - //if (image == null) - // return; + image.DataContextChanged += Image_DataContextChanged; - //image.DataContextChanged += Image_DataContextChanged; + if (image.DataContext is not PlaylistSong playlistSong) + return; - //var song = (PlaylistSong)image.DataContext; - - //if (song == null) - // return; - - //Task.Run(async () => await FetchImage(song.Song, image)); + //Task.Run(async () => await UpdateImage(image, playlistSong)); + UpdateImage(image, playlistSong); } private void Image_Unloaded(object sender, RoutedEventArgs e) { - //Image? image = sender as Image; + if (sender is not Image image) + return; - //if (image == null) - // return; + image.DataContextChanged -= Image_DataContextChanged; + } - //image.DataContextChanged -= Image_DataContextChanged; + private void Image_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + if (sender is not Image image) + return; + + if (args.NewValue is not PlaylistSong playlistSong) + return; + + //Task.Run(async () => await UpdateImage(image, playlistSong)); + UpdateImage(image, playlistSong); + } + + private void UpdateImage(Image image, PlaylistSong playlistSong) + { + int hashCode = image.GetHashCode(); + //BitmapImage? bitmapImage = await _viewModel.GetBitmapImageAsync(hashCode, playlistSong); + + DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () => + { + BitmapImage? bitmapImage = await _viewModel.GetBitmapImageAsync(hashCode, playlistSong); + image.Source = bitmapImage; + }); } private async void PlaylistListViewItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e) @@ -54,4 +122,45 @@ public sealed partial class PlaylistView : UserControl await _viewModel.PlaySongAsync(playlistSong); } + + private void PlaylistListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs e) + { + if (sender is not FrameworkElement element) + return; + + if (element == null || element.DataContext is not PlaylistSong playlistSong) + return; + + if (PlaylistListView.SelectedItems.Contains(playlistSong)) + return; + + int index = PlaylistListView.Items.IndexOf(playlistSong); + + PlaylistListView.DeselectAll(); + PlaylistListView.SelectRange(new ItemIndexRange(index, 1)); + } + + private void PlaylistListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is not ListView listView) + return; + + PlaylistSong[] selectedPlaylistSongs = [.. listView.SelectedItems.Cast()]; + + _viewModel.SelectedPlaylistSongs = [.. selectedPlaylistSongs]; + } + + private void MenuFlyout_Opening(object sender, object e) + { + if (sender is not MenuFlyout menuFlyout) + return; + + foreach (MenuFlyoutItemBase item in menuFlyout.Items) + { + if (item.DataContext != _viewModel) + { + item.DataContext = _viewModel; + } + } + } } \ No newline at end of file