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 Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using Windows.UI.Popups; namespace Harmonia.WinUI.Views; public sealed partial class PlaylistView : UserControl { private readonly PlaylistViewModel _viewModel; public PlaylistView() { InitializeComponent(); _viewModel = (PlaylistViewModel)DataContext; _viewModel.PropertyChanging += OnViewModelPropertyChanging; _viewModel.PropertyChanged += OnViewModelPropertyChanged; _viewModel.PlayingSongChangedAutomatically += OnPlayingSongChangedAutomatically; foreach (MenuFlyoutItemBase item in PlaylistListViewMenuFlyout.Items) { item.DataContextChanged += Item_DataContextChanged; } } private void OnViewModelPropertyChanging(object? sender, PropertyChangingEventArgs e) { if (e.PropertyName == nameof(_viewModel.PlayingSong)) { ListViewItem listViewItem = (ListViewItem)PlaylistListView.ContainerFromItem(_viewModel.PlayingSong); if (listViewItem != null) { UpdateListViewItemStyle(listViewItem, false); } } } private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(_viewModel.PlayingSong)) { ListViewItem listViewItem = (ListViewItem)PlaylistListView.ContainerFromItem(_viewModel.PlayingSong); UpdateListViewItemStyle(listViewItem, true); } } private void UpdateListViewItemStyle(FrameworkElement listViewItem, bool isPlaying) { if (listViewItem == null) return; FrameworkElement? frameworkElement; if (listViewItem.Name == "PlaylistSongListViewItem") { frameworkElement = listViewItem; } else { if (listViewItem.FindDescendant("PlaylistSongListViewItem") is not FrameworkElement frameworkElement2) return; frameworkElement = frameworkElement2; } if (frameworkElement == null) return; string songItemTitleBrushName = isPlaying ? "PlayingSongTitleTextBlock" : "SongTitleTextBlock"; string songItemSubtitleBrushName = isPlaying ? "PlayingSongSubtitleTextBlock" : "SongSubtitleTextBlock"; UpdateElementStyle(frameworkElement, "SongTitleTextBlock", songItemTitleBrushName); UpdateElementStyle(frameworkElement, "SongArtistsTextBlock", songItemSubtitleBrushName); UpdateElementStyle(frameworkElement, "SongAlbumTextBlock", songItemSubtitleBrushName); } private void UpdateElementStyle(FrameworkElement dependencyObject, string elementName, string resourceName) { if (dependencyObject.FindDescendant(elementName) is not FrameworkElement frameworkElement) return; Resources.TryGetValue(resourceName, out object? resource); if (resource == null) Application.Current.Resources.TryGetValue(resourceName, out resource); if (resource is not Style style) return; frameworkElement.Style = style; } 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) { if (sender is not Image image) return; image.DataContextChanged += Image_DataContextChanged; if (image.DataContext is not PlaylistSong playlistSong) return; //Task.Run(async () => await UpdateImage(image, playlistSong)); UpdateImage(image, playlistSong); } private void Image_Unloaded(object sender, RoutedEventArgs e) { if (sender is not Image image) return; 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 void PlaylistSongListViewItem_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) { if (args.NewValue is not PlaylistSong playlistSong) return; bool isPlaying = playlistSong == _viewModel.PlayingSong; UpdateListViewItemStyle(sender, isPlaying); } private async void PlaylistListViewItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e) { if (sender is not FrameworkElement element) return; if (element == null || element.DataContext is not PlaylistSong playlistSong) return; 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; } } } }