From 91e4d88643fe4e141a96e885f762068cd6a2b48f Mon Sep 17 00:00:00 2001 From: Brian Bicknell Date: Sun, 9 Aug 2026 17:30:38 -0400 Subject: [PATCH] Added playlists view context menu for rename, lock/unlock, and delete. --- Harmonia.Core/Playlists/PlaylistManager.cs | 5 ++ .../ViewModels/PlaylistDetailViewModel.cs | 12 --- .../ViewModels/PlaylistItemViewModel.cs | 14 +++ .../ViewModels/PlaylistsViewModel.cs | 85 ++++++++++++++++++- Harmonia.WinUI/Views/PlaylistsView.xaml | 43 +++++++++- Harmonia.WinUI/Views/PlaylistsView.xaml.cs | 10 +++ 6 files changed, 154 insertions(+), 15 deletions(-) diff --git a/Harmonia.Core/Playlists/PlaylistManager.cs b/Harmonia.Core/Playlists/PlaylistManager.cs index d036b52..cd64c37 100644 --- a/Harmonia.Core/Playlists/PlaylistManager.cs +++ b/Harmonia.Core/Playlists/PlaylistManager.cs @@ -82,6 +82,11 @@ public class PlaylistManager : IPlaylistManager playlistRepository.Delete(playlist); + if (CurrentPlaylist == playlist) + { + CurrentPlaylist = _playlists.Count > 0 ? _playlists[0] : null; + } + PlaylistRemoved?.Invoke(this, new(playlist)); } diff --git a/Harmonia.WinUI/ViewModels/PlaylistDetailViewModel.cs b/Harmonia.WinUI/ViewModels/PlaylistDetailViewModel.cs index fc211c5..0aed8de 100644 --- a/Harmonia.WinUI/ViewModels/PlaylistDetailViewModel.cs +++ b/Harmonia.WinUI/ViewModels/PlaylistDetailViewModel.cs @@ -681,18 +681,6 @@ public partial class PlaylistDetailViewModel : ViewModelBase return; _playlistManager.RemovePlaylist(Playlist); - - if (_playlistManager.CurrentPlaylist == Playlist) - { - if (_playlistManager.Playlists.Count > 0) - { - _playlistManager.CurrentPlaylist = _playlistManager.Playlists.ElementAt(0); - } - else - { - _playlistManager.CurrentPlaylist = null; - } - } } private void SortAllSongs(SortItem? sortItem) diff --git a/Harmonia.WinUI/ViewModels/PlaylistItemViewModel.cs b/Harmonia.WinUI/ViewModels/PlaylistItemViewModel.cs index 0862036..e5d3268 100644 --- a/Harmonia.WinUI/ViewModels/PlaylistItemViewModel.cs +++ b/Harmonia.WinUI/ViewModels/PlaylistItemViewModel.cs @@ -35,6 +35,19 @@ public partial class PlaylistItemViewModel : ObservableObject } } + private bool _isLocked; + public bool IsLocked + { + get + { + return _isLocked; + } + set + { + SetProperty(ref _isLocked, value); + } + } + public PlaylistItemViewModel(Playlist playlist) { Playlist = playlist; @@ -55,6 +68,7 @@ public partial class PlaylistItemViewModel : ObservableObject private void UpdateMetaData() { Name = Playlist.Name ?? "Untitled Playlist"; + IsLocked = Playlist.IsLocked; TimeSpan total = TimeSpan.FromSeconds(Playlist.Songs.Sum(s => s.Song.Length.TotalSeconds)); Summary = $"{Playlist.Songs.Count} songs / {total:h\\:mm\\:ss}"; diff --git a/Harmonia.WinUI/ViewModels/PlaylistsViewModel.cs b/Harmonia.WinUI/ViewModels/PlaylistsViewModel.cs index 7c16206..4adb65a 100644 --- a/Harmonia.WinUI/ViewModels/PlaylistsViewModel.cs +++ b/Harmonia.WinUI/ViewModels/PlaylistsViewModel.cs @@ -1,9 +1,12 @@ -using Harmonia.Core.Player; +using CommunityToolkit.Mvvm.Input; +using Harmonia.Core.Player; using Harmonia.Core.Playlists; +using Harmonia.WinUI.Messaging; using Microsoft.UI.Dispatching; using System; using System.Collections.ObjectModel; using System.Linq; +using System.Threading.Tasks; namespace Harmonia.WinUI.ViewModels; @@ -11,6 +14,7 @@ public partial class PlaylistsViewModel : ViewModelBase { private readonly IPlaylistManager _playlistManager; private readonly IAudioPlayer _audioPlayer; + private readonly IMessageService _messageService; private readonly DispatcherQueue _dispatcherQueue; private ObservableCollection _playlists = []; @@ -55,12 +59,31 @@ public partial class PlaylistsViewModel : ViewModelBase } } - public PlaylistsViewModel(IPlaylistManager playlistManager, IAudioPlayer audioPlayer) + private PlaylistItemViewModel? _flyoutPlaylist = null; + public PlaylistItemViewModel? FlyoutPlaylist + { + get + { + return _flyoutPlaylist; + } + set + { + SetProperty(ref _flyoutPlaylist, value); + } + } + + public IAsyncRelayCommand RenamePlaylistCommand { get; } + public IRelayCommand LockPlaylistCommand { get; } + public IRelayCommand UnlockPlaylistCommand { get; } + public IAsyncRelayCommand DeletePlaylistCommand { get; } + + public PlaylistsViewModel(IPlaylistManager playlistManager, IAudioPlayer audioPlayer, IMessageService messageService) { _playlistManager = playlistManager; _playlistManager.PlaylistAdded += OnPlaylistAdded; _playlistManager.PlaylistRemoved += OnPlaylistRemoved; _playlistManager.CurrentPlaylistChanged += OnCurrentPlaylistChanged; + _messageService = messageService; _audioPlayer = audioPlayer; _audioPlayer.PlayingSongChanged += OnPlayingSongChanged; @@ -70,6 +93,11 @@ public partial class PlaylistsViewModel : ViewModelBase _playlists = new ObservableCollection(_playlistManager.Playlists.Select(p => new PlaylistItemViewModel(p))); _selectedPlaylist = _playlists.FirstOrDefault(pv => pv.Playlist == _playlistManager.CurrentPlaylist); _activePlaylist = _playlists.FirstOrDefault(pv => pv.Playlist == _audioPlayer.Playlist); + + RenamePlaylistCommand = new AsyncRelayCommand(RenamePlaylist); + LockPlaylistCommand = new RelayCommand(LockPlaylist); + UnlockPlaylistCommand = new RelayCommand(UnlockPlaylist); + DeletePlaylistCommand = new AsyncRelayCommand(DeletePlaylistAsync); } private void OnCurrentPlaylistChanged(object? sender, EventArgs e) @@ -102,4 +130,57 @@ public partial class PlaylistsViewModel : ViewModelBase { ActivePlaylist = Playlists.FirstOrDefault(pv => pv.Playlist == _audioPlayer.Playlist); } + + private async Task RenamePlaylist(PlaylistItemViewModel? playlistItem) + { + if (playlistItem is null || playlistItem.Playlist is null) + return; + + Playlist playlist = playlistItem.Playlist; + + string newName = await _messageService.InputTextAsync( + $"Rename Playlist - {playlist.Name}", + "Enter a new name for the playlist:", + playlist.Name ?? string.Empty); + + if (string.IsNullOrWhiteSpace(newName)) + return; + + playlist.SetName(newName); + } + + private void LockPlaylist(PlaylistItemViewModel? playlistItem) + { + if (playlistItem is null || playlistItem.Playlist is null) + return; + + Playlist playlist = playlistItem.Playlist; + + playlist.Lock(); + } + + private void UnlockPlaylist(PlaylistItemViewModel? playlistItem) + { + if (playlistItem is null || playlistItem.Playlist is null) + return; + + Playlist playlist = playlistItem.Playlist; + + playlist.Unlock(); + } + + private async Task DeletePlaylistAsync(PlaylistItemViewModel? playlistItem) + { + if (playlistItem is null || playlistItem.Playlist is null) + return; + + Playlist playlist = playlistItem.Playlist; + + bool confirmed = await _messageService.ConfirmAsync($"Delete Playlist - {playlist.Name}", "Are you sure you want to delete this playlist?"); + + if (!confirmed) + return; + + _playlistManager.RemovePlaylist(playlist); + } } \ No newline at end of file diff --git a/Harmonia.WinUI/Views/PlaylistsView.xaml b/Harmonia.WinUI/Views/PlaylistsView.xaml index 7539558..d1e2203 100644 --- a/Harmonia.WinUI/Views/PlaylistsView.xaml +++ b/Harmonia.WinUI/Views/PlaylistsView.xaml @@ -7,11 +7,15 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:Harmonia.WinUI.ViewModels" + xmlns:converter="using:Harmonia.WinUI.Converters" xmlns:playlists="using:Harmonia.Core.Playlists" DataContext="{Binding Source={StaticResource Locator}, Path=PlaylistsViewModel}" d:DataContext="{d:DesignInstance Type=vm:PlaylistsViewModel, IsDesignTimeCreatable=True}" mc:Ignorable="d"> + + + @@ -76,7 +80,44 @@ Name="PlaylistsListView" ItemsSource="{Binding Playlists, Mode=OneWay}" SelectedItem="{Binding SelectedPlaylist, Mode=TwoWay}" - ItemTemplate="{StaticResource PlaylistTemplate}"> + ItemTemplate="{StaticResource PlaylistTemplate}" + RightTapped="PlaylistsListView_RightTapped"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Harmonia.WinUI/Views/PlaylistsView.xaml.cs b/Harmonia.WinUI/Views/PlaylistsView.xaml.cs index 0ca3027..a0668fa 100644 --- a/Harmonia.WinUI/Views/PlaylistsView.xaml.cs +++ b/Harmonia.WinUI/Views/PlaylistsView.xaml.cs @@ -19,6 +19,16 @@ public sealed partial class PlaylistsView : UserControl _viewModel.PropertyChanged += OnViewModelPropertyChanged; PlaylistsListView.ContainerContentChanging += OnContainerContentChanging; + + foreach (MenuFlyoutItemBase item in PlaylistListViewMenuFlyout.Items) + { + item.DataContext = _viewModel; + } + } + + private void PlaylistsListView_RightTapped(object sender, Microsoft.UI.Xaml.Input.RightTappedRoutedEventArgs e) + { + _viewModel.FlyoutPlaylist = (e.OriginalSource as FrameworkElement)?.DataContext as PlaylistItemViewModel; } private void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)