356 lines
11 KiB
C#
356 lines
11 KiB
C#
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.Imaging;
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
|
|
namespace Harmonia.WinUI.Views;
|
|
|
|
public sealed partial class PlaylistDetailView : UserControl
|
|
{
|
|
private readonly PlaylistDetailViewModel _viewModel;
|
|
|
|
public PlaylistDetailView()
|
|
{
|
|
InitializeComponent();
|
|
|
|
_viewModel = (PlaylistDetailViewModel)DataContext;
|
|
_viewModel.PropertyChanging += OnViewModelPropertyChanging;
|
|
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
|
|
_viewModel.PlayingSongChangedAutomatically += OnPlayingSongChangedAutomatically;
|
|
|
|
foreach (MenuFlyoutItemBase item in PlaylistListViewMenuFlyout.Items)
|
|
{
|
|
HookDataContextChanged(item);
|
|
}
|
|
|
|
int currentSortItemIndex = 0;
|
|
|
|
foreach (SortItem sortItem in _viewModel.SortItems)
|
|
{
|
|
SortByMenuFlyoutSubItem.Items.Insert(currentSortItemIndex, new MenuFlyoutItem
|
|
{
|
|
Text = sortItem.Name,
|
|
Command = _viewModel.SortAllCommand,
|
|
CommandParameter = sortItem
|
|
});
|
|
|
|
SortSelectedByMenuFlyoutSubItem.Items.Insert(currentSortItemIndex, new MenuFlyoutItem
|
|
{
|
|
Text = sortItem.Name,
|
|
Command = _viewModel.SortSelectedCommand,
|
|
CommandParameter = sortItem
|
|
});
|
|
|
|
currentSortItemIndex++;
|
|
}
|
|
|
|
_viewModel.SortSelectedCommand.CanExecuteChanged += (_, _) =>
|
|
SortSelectedByMenuFlyoutSubItem.IsEnabled = _viewModel.SortSelectedCommand.CanExecute(null);
|
|
|
|
SortSelectedByMenuFlyoutSubItem.IsEnabled = _viewModel.SortSelectedCommand.CanExecute(null);
|
|
|
|
Loaded += OnLoaded;
|
|
}
|
|
|
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
|
{
|
|
Loaded -= OnLoaded;
|
|
|
|
// Defer until the ListView has completed its first layout/container realization pass.
|
|
DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, BringPlayingSongIntoView);
|
|
}
|
|
|
|
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)
|
|
{
|
|
switch (e.PropertyName)
|
|
{
|
|
case nameof(_viewModel.PlayingSong):
|
|
ListViewItem listViewItem = (ListViewItem)PlaylistListView.ContainerFromItem(_viewModel.PlayingSong);
|
|
UpdateListViewItemStyle(listViewItem, true);
|
|
break;
|
|
case nameof(_viewModel.Playlist):
|
|
ScrollToTop();
|
|
break;
|
|
case nameof(_viewModel.SelectedPlaylistSongs):
|
|
SyncListViewSelection();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private bool _isSyncingSelection;
|
|
|
|
private void SyncListViewSelection()
|
|
{
|
|
_isSyncingSelection = true;
|
|
|
|
try
|
|
{
|
|
PlaylistListView.DeselectAll();
|
|
|
|
foreach (PlaylistSong playlistSong in _viewModel.SelectedPlaylistSongs)
|
|
{
|
|
int index = PlaylistListView.Items.IndexOf(playlistSong);
|
|
|
|
if (index >= 0)
|
|
{
|
|
PlaylistListView.SelectRange(new ItemIndexRange(index, 1));
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_isSyncingSelection = false;
|
|
}
|
|
}
|
|
|
|
private void ScrollToTop()
|
|
{
|
|
if (PlaylistListView.FindDescendant<ScrollViewer>() is ScrollViewer scrollViewer)
|
|
{
|
|
scrollViewer.ChangeView(null, 0, null, disableAnimation: false);
|
|
}
|
|
else if (_viewModel.FilteredPlaylistSongs.FirstOrDefault() is PlaylistSong firstSong)
|
|
{
|
|
PlaylistListView.ScrollIntoView(firstSong);
|
|
}
|
|
}
|
|
|
|
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 HookDataContextChanged(MenuFlyoutItemBase item)
|
|
{
|
|
item.DataContextChanged += Item_DataContextChanged;
|
|
|
|
if (item is MenuFlyoutSubItem subItem)
|
|
{
|
|
foreach (MenuFlyoutItemBase childItem in subItem.Items)
|
|
{
|
|
HookDataContextChanged(childItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
//SongPictureInfo? songPictureInfo = await _viewModel.GetSongPictureInfoAsync(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.UID == _viewModel.PlayingSong?.UID;
|
|
|
|
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 (_isSyncingSelection)
|
|
return;
|
|
|
|
if (sender is not ListView listView)
|
|
return;
|
|
|
|
PlaylistSong[] selectedPlaylistSongs = [.. listView.SelectedItems.Cast<PlaylistSong>()];
|
|
|
|
_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;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PlaylistListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
|
|
{
|
|
_viewModel.IsUserUpdating = true;
|
|
}
|
|
|
|
private void PlaylistListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
|
{
|
|
_viewModel.IsUserUpdating = false;
|
|
}
|
|
} |