Highlight active playlist. Various optimizations and fixes.
This commit is contained in:
@@ -173,7 +173,7 @@ public class AudioPlayer : IAudioPlayer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int currentIndex = Playlist.Songs.IndexOf(CurrentPlaylistSong);
|
int currentIndex = Playlist.IndexOf(CurrentPlaylistSong);
|
||||||
int nextIndex = currentIndex + 1;
|
int nextIndex = currentIndex + 1;
|
||||||
|
|
||||||
if (nextIndex > Playlist.Songs.Count - 1)
|
if (nextIndex > Playlist.Songs.Count - 1)
|
||||||
@@ -200,7 +200,7 @@ public class AudioPlayer : IAudioPlayer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int currentIndex = Playlist.Songs.IndexOf(CurrentPlaylistSong);
|
int currentIndex = Playlist.IndexOf(CurrentPlaylistSong);
|
||||||
int nextIndex = currentIndex - 1;
|
int nextIndex = currentIndex - 1;
|
||||||
|
|
||||||
if (nextIndex < 0)
|
if (nextIndex < 0)
|
||||||
@@ -233,7 +233,7 @@ public class AudioPlayer : IAudioPlayer
|
|||||||
{
|
{
|
||||||
if (Playlist == null || Playlist.Songs.Contains(song) == false)
|
if (Playlist == null || Playlist.Songs.Contains(song) == false)
|
||||||
{
|
{
|
||||||
Playlist? newPlaylist = _playlistManager.GetPlaylist(song);
|
Playlist? newPlaylist = _playlistManager.FindPlaylistContaining(song);
|
||||||
|
|
||||||
if (newPlaylist == null)
|
if (newPlaylist == null)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public interface IPlaylistManager
|
|||||||
Task InitializeAsync();
|
Task InitializeAsync();
|
||||||
Task<Playlist> AddPlaylistAsync();
|
Task<Playlist> AddPlaylistAsync();
|
||||||
void RemovePlaylist(Playlist playlist);
|
void RemovePlaylist(Playlist playlist);
|
||||||
Playlist? GetPlaylist(PlaylistSong playlistSong);
|
Playlist? FindPlaylistContaining(PlaylistSong playlistSong);
|
||||||
|
|
||||||
event EventHandler? CurrentPlaylistChanged;
|
event EventHandler? CurrentPlaylistChanged;
|
||||||
event EventHandler<PlaylistAddedEventArgs> PlaylistAdded;
|
event EventHandler<PlaylistAddedEventArgs> PlaylistAdded;
|
||||||
|
|||||||
@@ -4,10 +4,5 @@ namespace Harmonia.Core.Playlists;
|
|||||||
|
|
||||||
public interface IPlaylistRepository : IRepository<Playlist>
|
public interface IPlaylistRepository : IRepository<Playlist>
|
||||||
{
|
{
|
||||||
Playlist? GetPlaylist(PlaylistSong playlistSong);
|
|
||||||
//void AddPlaylist();
|
|
||||||
//void RemovePlaylist(Playlist playlist);
|
|
||||||
|
|
||||||
//event EventHandler<PlaylistAddedEventArgs> PlaylistAdded;
|
|
||||||
//event EventHandler<PlaylistRemovedEventArgs> PlaylistRemoved;
|
|
||||||
}
|
}
|
||||||
@@ -5,15 +5,48 @@ namespace Harmonia.Core.Playlists;
|
|||||||
|
|
||||||
public class Playlist
|
public class Playlist
|
||||||
{
|
{
|
||||||
|
private readonly List<PlaylistSong> _songs = [];
|
||||||
|
private readonly List<GroupOption> _groupOptions = [];
|
||||||
|
private readonly List<SortOption> _sortOptions = [];
|
||||||
|
|
||||||
public string UID { get; init; } = Guid.NewGuid().ToString();
|
public string UID { get; init; } = Guid.NewGuid().ToString();
|
||||||
public string? Name { get; set; }
|
public string? Name { get; private set; }
|
||||||
public List<PlaylistSong> Songs { get; init; } = []; // TODO: Change to "private init" once deserialization is fixed
|
public IReadOnlyList<PlaylistSong> Songs => _songs;
|
||||||
public List<GroupOption> GroupOptions { get; set; } = [];
|
public IReadOnlyList<GroupOption> GroupOptions => _groupOptions;
|
||||||
public List<SortOption> SortOptions { get; set; } = [];
|
public IReadOnlyList<SortOption> SortOptions => _sortOptions;
|
||||||
public bool IsLocked { get; set; }
|
public bool IsLocked { get; private set; }
|
||||||
|
|
||||||
public event EventHandler<PlaylistUpdatedEventArgs>? PlaylistUpdated;
|
public event EventHandler<PlaylistUpdatedEventArgs>? PlaylistUpdated;
|
||||||
|
|
||||||
|
public Playlist()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Playlist(string? name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Playlist Restore(string uid, string? name, IEnumerable<PlaylistSong> songs, IEnumerable<GroupOption> groupOptions, IEnumerable<SortOption> sortOptions, bool isLocked)
|
||||||
|
{
|
||||||
|
Playlist playlist = new(name)
|
||||||
|
{
|
||||||
|
UID = uid,
|
||||||
|
IsLocked = isLocked
|
||||||
|
};
|
||||||
|
|
||||||
|
playlist._songs.AddRange(songs);
|
||||||
|
playlist._groupOptions.AddRange(groupOptions);
|
||||||
|
playlist._sortOptions.AddRange(sortOptions);
|
||||||
|
|
||||||
|
return playlist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexOf(PlaylistSong playlistSong)
|
||||||
|
{
|
||||||
|
return _songs.IndexOf(playlistSong);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetName(string name)
|
public void SetName(string name)
|
||||||
{
|
{
|
||||||
if (string.Equals(Name, name, StringComparison.Ordinal))
|
if (string.Equals(Name, name, StringComparison.Ordinal))
|
||||||
@@ -88,9 +121,9 @@ public class Playlist
|
|||||||
if (playlistSongs.Length == 0)
|
if (playlistSongs.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int insertIndex = index ?? Songs.Count;
|
int insertIndex = index ?? _songs.Count;
|
||||||
|
|
||||||
Songs.InsertRange(insertIndex, playlistSongs);
|
_songs.InsertRange(insertIndex, playlistSongs);
|
||||||
|
|
||||||
PlaylistUpdatedEventArgs eventArgs = new()
|
PlaylistUpdatedEventArgs eventArgs = new()
|
||||||
{
|
{
|
||||||
@@ -105,7 +138,7 @@ public class Playlist
|
|||||||
|
|
||||||
public void MoveSong(PlaylistSong playlistSong, int newIndex)
|
public void MoveSong(PlaylistSong playlistSong, int newIndex)
|
||||||
{
|
{
|
||||||
int currentIndex = Songs.IndexOf(playlistSong);
|
int currentIndex = _songs.IndexOf(playlistSong);
|
||||||
|
|
||||||
MoveSong(currentIndex, newIndex);
|
MoveSong(currentIndex, newIndex);
|
||||||
}
|
}
|
||||||
@@ -118,10 +151,10 @@ public class Playlist
|
|||||||
if (oldIndex == newIndex)
|
if (oldIndex == newIndex)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PlaylistSong playlistSong = Songs[oldIndex];
|
PlaylistSong playlistSong = _songs[oldIndex];
|
||||||
|
|
||||||
Songs.Remove(playlistSong);
|
_songs.Remove(playlistSong);
|
||||||
Songs.Insert(newIndex, playlistSong);
|
_songs.Insert(newIndex, playlistSong);
|
||||||
|
|
||||||
PlaylistUpdatedEventArgs eventArgs = new()
|
PlaylistUpdatedEventArgs eventArgs = new()
|
||||||
{
|
{
|
||||||
@@ -141,8 +174,8 @@ public class Playlist
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Dictionary<int, PlaylistSong> oldPlaylistSongs = playlistSongs
|
Dictionary<int, PlaylistSong> oldPlaylistSongs = playlistSongs
|
||||||
.OrderBy(Songs.IndexOf)
|
.OrderBy(_songs.IndexOf)
|
||||||
.ToDictionary(Songs.IndexOf, playlistSong => playlistSong);
|
.ToDictionary(_songs.IndexOf, playlistSong => playlistSong);
|
||||||
|
|
||||||
Song[] songs = [.. playlistSongs.Select(playlistSong => playlistSong.Song)];
|
Song[] songs = [.. playlistSongs.Select(playlistSong => playlistSong.Song)];
|
||||||
Song[] sortedSongs = [.. songs.SortBy(sortOptions)];
|
Song[] sortedSongs = [.. songs.SortBy(sortOptions)];
|
||||||
@@ -158,8 +191,8 @@ public class Playlist
|
|||||||
if (newPlaylistSong == playlistSong)
|
if (newPlaylistSong == playlistSong)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Songs.RemoveAt(index);
|
_songs.RemoveAt(index);
|
||||||
Songs.Insert(index, newPlaylistSong);
|
_songs.Insert(index, newPlaylistSong);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistUpdatedEventArgs eventArgs = new()
|
PlaylistUpdatedEventArgs eventArgs = new()
|
||||||
@@ -178,11 +211,11 @@ public class Playlist
|
|||||||
if (IsLocked)
|
if (IsLocked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int[] originalIndexes = [.. playlistSongs.Select(playlistSong => Songs.IndexOf(playlistSong))];
|
int[] originalIndexes = [.. playlistSongs.Select(playlistSong => _songs.IndexOf(playlistSong))];
|
||||||
PlaylistSong[] shuffledSongs = [.. playlistSongs.Shuffle()];
|
PlaylistSong[] shuffledSongs = [.. playlistSongs.Shuffle()];
|
||||||
|
|
||||||
for (int i = 0; i < originalIndexes.Length; i++)
|
for (int i = 0; i < originalIndexes.Length; i++)
|
||||||
Songs[originalIndexes[i]] = shuffledSongs[i];
|
_songs[originalIndexes[i]] = shuffledSongs[i];
|
||||||
|
|
||||||
PlaylistUpdatedEventArgs eventArgs = new()
|
PlaylistUpdatedEventArgs eventArgs = new()
|
||||||
{
|
{
|
||||||
@@ -200,11 +233,11 @@ public class Playlist
|
|||||||
if (IsLocked)
|
if (IsLocked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int[] originalIndexes = [.. playlistSongs.Select(playlistSong => Songs.IndexOf(playlistSong))];
|
int[] originalIndexes = [.. playlistSongs.Select(playlistSong => _songs.IndexOf(playlistSong))];
|
||||||
PlaylistSong[] reversedSongs = [.. originalIndexes.Select(i => Songs[i]).Reverse()];
|
PlaylistSong[] reversedSongs = [.. originalIndexes.Select(i => _songs[i]).Reverse()];
|
||||||
|
|
||||||
for (int i = 0; i < originalIndexes.Length; i++)
|
for (int i = 0; i < originalIndexes.Length; i++)
|
||||||
Songs[originalIndexes[i]] = reversedSongs[i];
|
_songs[originalIndexes[i]] = reversedSongs[i];
|
||||||
|
|
||||||
PlaylistUpdatedEventArgs eventArgs = new()
|
PlaylistUpdatedEventArgs eventArgs = new()
|
||||||
{
|
{
|
||||||
@@ -224,7 +257,7 @@ public class Playlist
|
|||||||
|
|
||||||
public void RemoveSongs(int index, int count)
|
public void RemoveSongs(int index, int count)
|
||||||
{
|
{
|
||||||
PlaylistSong[] playlistSongs = [.. Songs.GetRange(index, count)];
|
PlaylistSong[] playlistSongs = [.. _songs.GetRange(index, count)];
|
||||||
|
|
||||||
RemoveSongs(playlistSongs);
|
RemoveSongs(playlistSongs);
|
||||||
}
|
}
|
||||||
@@ -243,7 +276,7 @@ public class Playlist
|
|||||||
|
|
||||||
foreach (PlaylistSong playlistSong in playlistSongs)
|
foreach (PlaylistSong playlistSong in playlistSongs)
|
||||||
{
|
{
|
||||||
if (Songs.Remove(playlistSong))
|
if (_songs.Remove(playlistSong))
|
||||||
{
|
{
|
||||||
removedSongs.Add(playlistSong);
|
removedSongs.Add(playlistSong);
|
||||||
}
|
}
|
||||||
|
|||||||
60
Harmonia.Core/Playlists/PlaylistDto.cs
Normal file
60
Harmonia.Core/Playlists/PlaylistDto.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using Harmonia.Core.Models;
|
||||||
|
|
||||||
|
namespace Harmonia.Core.Playlists;
|
||||||
|
|
||||||
|
internal sealed record PlaylistDto
|
||||||
|
{
|
||||||
|
public string UID { get; init; } = Guid.NewGuid().ToString();
|
||||||
|
public string? Name { get; init; }
|
||||||
|
public List<PlaylistSongDto> Songs { get; init; } = [];
|
||||||
|
public List<GroupOption> GroupOptions { get; init; } = [];
|
||||||
|
public List<SortOption> SortOptions { get; init; } = [];
|
||||||
|
public bool IsLocked { get; init; }
|
||||||
|
|
||||||
|
public static PlaylistDto FromPlaylist(Playlist playlist)
|
||||||
|
{
|
||||||
|
return new PlaylistDto
|
||||||
|
{
|
||||||
|
UID = playlist.UID,
|
||||||
|
Name = playlist.Name,
|
||||||
|
Songs = [.. playlist.Songs.Select(PlaylistSongDto.FromPlaylistSong)],
|
||||||
|
GroupOptions = [.. playlist.GroupOptions],
|
||||||
|
SortOptions = [.. playlist.SortOptions],
|
||||||
|
IsLocked = playlist.IsLocked
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Playlist ToPlaylist()
|
||||||
|
{
|
||||||
|
return Playlist.Restore(
|
||||||
|
UID,
|
||||||
|
Name,
|
||||||
|
Songs.Select(song => song.ToPlaylistSong()),
|
||||||
|
GroupOptions,
|
||||||
|
SortOptions,
|
||||||
|
IsLocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record PlaylistSongDto
|
||||||
|
{
|
||||||
|
public string UID { get; init; } = Guid.NewGuid().ToString();
|
||||||
|
public required Song Song { get; init; }
|
||||||
|
|
||||||
|
public static PlaylistSongDto FromPlaylistSong(PlaylistSong playlistSong)
|
||||||
|
{
|
||||||
|
return new PlaylistSongDto
|
||||||
|
{
|
||||||
|
UID = playlistSong.UID,
|
||||||
|
Song = playlistSong.Song
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlaylistSong ToPlaylistSong()
|
||||||
|
{
|
||||||
|
return new PlaylistSong(Song)
|
||||||
|
{
|
||||||
|
UID = UID
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ public class PlaylistManager : IPlaylistManager
|
|||||||
{
|
{
|
||||||
private readonly IPlaylistRepository playlistRepository;
|
private readonly IPlaylistRepository playlistRepository;
|
||||||
private readonly List<Playlist> _playlists = [];
|
private readonly List<Playlist> _playlists = [];
|
||||||
|
private readonly Dictionary<string, Playlist> _playlistsBySongUid = [];
|
||||||
|
|
||||||
public IReadOnlyList<Playlist> Playlists => _playlists;
|
public IReadOnlyList<Playlist> Playlists => _playlists;
|
||||||
|
|
||||||
@@ -37,6 +38,11 @@ public class PlaylistManager : IPlaylistManager
|
|||||||
foreach (Playlist playlist in _playlists)
|
foreach (Playlist playlist in _playlists)
|
||||||
{
|
{
|
||||||
playlist.PlaylistUpdated += OnPlaylistUpdated;
|
playlist.PlaylistUpdated += OnPlaylistUpdated;
|
||||||
|
|
||||||
|
foreach (PlaylistSong song in playlist.Songs)
|
||||||
|
{
|
||||||
|
_playlistsBySongUid[song.UID] = playlist;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentPlaylist = _playlists.Count > 0 ? _playlists[0] : await AddPlaylistAsync();
|
CurrentPlaylist = _playlists.Count > 0 ? _playlists[0] : await AddPlaylistAsync();
|
||||||
@@ -44,14 +50,16 @@ public class PlaylistManager : IPlaylistManager
|
|||||||
|
|
||||||
public async Task<Playlist> AddPlaylistAsync()
|
public async Task<Playlist> AddPlaylistAsync()
|
||||||
{
|
{
|
||||||
Playlist playlist = new()
|
Playlist playlist = new("New Playlist");
|
||||||
{
|
|
||||||
Name = "New Playlist"
|
|
||||||
};
|
|
||||||
|
|
||||||
playlist.PlaylistUpdated += OnPlaylistUpdated;
|
playlist.PlaylistUpdated += OnPlaylistUpdated;
|
||||||
_playlists.Add(playlist);
|
_playlists.Add(playlist);
|
||||||
|
|
||||||
|
foreach (PlaylistSong song in playlist.Songs)
|
||||||
|
{
|
||||||
|
_playlistsBySongUid[song.UID] = playlist;
|
||||||
|
}
|
||||||
|
|
||||||
await playlistRepository.SaveAsync(playlist);
|
await playlistRepository.SaveAsync(playlist);
|
||||||
|
|
||||||
PlaylistAdded?.Invoke(this, new(playlist));
|
PlaylistAdded?.Invoke(this, new(playlist));
|
||||||
@@ -64,14 +72,19 @@ public class PlaylistManager : IPlaylistManager
|
|||||||
playlist.PlaylistUpdated -= OnPlaylistUpdated;
|
playlist.PlaylistUpdated -= OnPlaylistUpdated;
|
||||||
_playlists.Remove(playlist);
|
_playlists.Remove(playlist);
|
||||||
|
|
||||||
|
foreach (PlaylistSong song in playlist.Songs)
|
||||||
|
{
|
||||||
|
_playlistsBySongUid.Remove(song.UID);
|
||||||
|
}
|
||||||
|
|
||||||
playlistRepository.Delete(playlist);
|
playlistRepository.Delete(playlist);
|
||||||
|
|
||||||
PlaylistRemoved?.Invoke(this, new(playlist));
|
PlaylistRemoved?.Invoke(this, new(playlist));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Playlist? GetPlaylist(PlaylistSong playlistSong)
|
public Playlist? FindPlaylistContaining(PlaylistSong playlistSong)
|
||||||
{
|
{
|
||||||
return _playlists.FirstOrDefault(playlist => playlist.Songs.Any(song => song.UID == playlistSong.UID));
|
return _playlistsBySongUid.GetValueOrDefault(playlistSong.UID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e)
|
private async void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e)
|
||||||
@@ -79,6 +92,22 @@ public class PlaylistManager : IPlaylistManager
|
|||||||
if (sender is not Playlist playlist)
|
if (sender is not Playlist playlist)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case PlaylistUpdateAction.Add:
|
||||||
|
foreach (PlaylistSong song in e.Songs)
|
||||||
|
{
|
||||||
|
_playlistsBySongUid[song.UID] = playlist;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PlaylistUpdateAction.Remove:
|
||||||
|
foreach (PlaylistSong song in e.Songs)
|
||||||
|
{
|
||||||
|
_playlistsBySongUid.Remove(song.UID);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
await playlistRepository.SaveAsync(playlist);
|
await playlistRepository.SaveAsync(playlist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,32 @@
|
|||||||
using Harmonia.Core.Data;
|
using Harmonia.Core.Data;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Harmonia.Core.Playlists;
|
namespace Harmonia.Core.Playlists;
|
||||||
|
|
||||||
public class PlaylistRepository : JsonFileRepository<Playlist>, IPlaylistRepository
|
public class PlaylistRepository : JsonFileRepository<Playlist>, IPlaylistRepository
|
||||||
{
|
{
|
||||||
|
private static readonly JsonSerializerOptions _options = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
IgnoreReadOnlyProperties = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||||
|
};
|
||||||
|
|
||||||
protected override string DirectoryName => Path.Combine("Playlists");
|
protected override string DirectoryName => Path.Combine("Playlists");
|
||||||
|
|
||||||
public Playlist? GetPlaylist(PlaylistSong playlistSong)
|
protected override async Task<Playlist> DeserializeAsync(Stream stream)
|
||||||
{
|
{
|
||||||
return Get().FirstOrDefault(playlist => playlist.Songs.Contains(playlistSong));
|
PlaylistDto dto = await JsonSerializer.DeserializeAsync<PlaylistDto>(stream, _options) ?? new();
|
||||||
|
|
||||||
|
return dto.ToPlaylist();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<string> SerializeAsync(Playlist playlist)
|
||||||
|
{
|
||||||
|
PlaylistDto dto = PlaylistDto.FromPlaylist(playlist);
|
||||||
|
|
||||||
|
return Task.FromResult(JsonSerializer.Serialize(dto, _options));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetNewFileName()
|
protected override string GetNewFileName()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistManager playli
|
|||||||
if (Playlist == null || PlayingSong == null)
|
if (Playlist == null || PlayingSong == null)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return Playlist.Songs.IndexOf(PlayingSong);
|
return Playlist.IndexOf(PlayingSong);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,10 +54,7 @@ public class AudioPlayerTests
|
|||||||
new Song() { FileName = "Song7.mp3" }
|
new Song() { FileName = "Song7.mp3" }
|
||||||
];
|
];
|
||||||
|
|
||||||
Playlist playlist = new()
|
Playlist playlist = new("Playlist1");
|
||||||
{
|
|
||||||
Name = "Playlist1"
|
|
||||||
};
|
|
||||||
|
|
||||||
playlist.AddSongs(songs);
|
playlist.AddSongs(songs);
|
||||||
|
|
||||||
@@ -65,7 +62,7 @@ public class AudioPlayerTests
|
|||||||
|
|
||||||
_playlistManager = Substitute.For<IPlaylistManager>();
|
_playlistManager = Substitute.For<IPlaylistManager>();
|
||||||
_playlistManager.Playlists.Returns([playlist]);
|
_playlistManager.Playlists.Returns([playlist]);
|
||||||
_playlistManager.GetPlaylist(Arg.Any<PlaylistSong>()).Returns(playlist);
|
_playlistManager.FindPlaylistContaining(Arg.Any<PlaylistSong>()).Returns(playlist);
|
||||||
|
|
||||||
_audioPlayer = new TestAudioPlayer(_audioEngine, _playlistManager);
|
_audioPlayer = new TestAudioPlayer(_audioEngine, _playlistManager);
|
||||||
_audioPlayer.SetPlaylist(playlist);
|
_audioPlayer.SetPlaylist(playlist);
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ public class PlaylistTests
|
|||||||
|
|
||||||
playlist.AddSongs(songs);
|
playlist.AddSongs(songs);
|
||||||
|
|
||||||
playlist.IsLocked = true;
|
playlist.Lock();
|
||||||
|
|
||||||
Song song = new() { FileName = "Song4.mp3" };
|
Song song = new() { FileName = "Song4.mp3" };
|
||||||
playlist.AddSong(song);
|
playlist.AddSong(song);
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ public class PlaylistViewModel : ViewModelBase
|
|||||||
if (Playlist == null || SelectedPlaylistSongs.Count == 0)
|
if (Playlist == null || SelectedPlaylistSongs.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int selectedPlaylistSongIndex = Playlist.Songs.IndexOf(SelectedPlaylistSongs[0]);
|
int selectedPlaylistSongIndex = Playlist.IndexOf(SelectedPlaylistSongs[0]);
|
||||||
|
|
||||||
if (selectedPlaylistSongIndex == -1)
|
if (selectedPlaylistSongIndex == -1)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -611,7 +611,7 @@ public partial class PlaylistDetailViewModel : ViewModelBase
|
|||||||
if (Playlist == null || SelectedPlaylistSongs.Count == 0)
|
if (Playlist == null || SelectedPlaylistSongs.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int selectedPlaylistSongIndex = Playlist.Songs.IndexOf(SelectedPlaylistSongs[0]);
|
int selectedPlaylistSongIndex = Playlist.IndexOf(SelectedPlaylistSongs[0]);
|
||||||
|
|
||||||
if (selectedPlaylistSongIndex == -1)
|
if (selectedPlaylistSongIndex == -1)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using Harmonia.Core.Player;
|
||||||
using Harmonia.Core.Playlists;
|
using Harmonia.Core.Playlists;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@@ -9,6 +9,7 @@ namespace Harmonia.WinUI.ViewModels;
|
|||||||
public partial class PlaylistsViewModel : ViewModelBase
|
public partial class PlaylistsViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly IPlaylistManager _playlistManager;
|
private readonly IPlaylistManager _playlistManager;
|
||||||
|
private readonly IAudioPlayer _audioPlayer;
|
||||||
|
|
||||||
private ObservableCollection<PlaylistItemViewModel> _playlists = [];
|
private ObservableCollection<PlaylistItemViewModel> _playlists = [];
|
||||||
public ObservableCollection<PlaylistItemViewModel> Playlists
|
public ObservableCollection<PlaylistItemViewModel> Playlists
|
||||||
@@ -39,13 +40,29 @@ public partial class PlaylistsViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlaylistsViewModel(IPlaylistManager playlistManager)
|
private PlaylistItemViewModel? _activePlaylist = null;
|
||||||
|
public PlaylistItemViewModel? ActivePlaylist
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _activePlaylist;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _activePlaylist, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlaylistsViewModel(IPlaylistManager playlistManager, IAudioPlayer audioPlayer)
|
||||||
{
|
{
|
||||||
_playlistManager = playlistManager;
|
_playlistManager = playlistManager;
|
||||||
_playlistManager.PlaylistAdded += OnPlaylistAdded;
|
_playlistManager.PlaylistAdded += OnPlaylistAdded;
|
||||||
_playlistManager.PlaylistRemoved += OnPlaylistRemoved;
|
_playlistManager.PlaylistRemoved += OnPlaylistRemoved;
|
||||||
_playlistManager.CurrentPlaylistChanged += OnCurrentPlaylistChanged;
|
_playlistManager.CurrentPlaylistChanged += OnCurrentPlaylistChanged;
|
||||||
|
|
||||||
|
_audioPlayer = audioPlayer;
|
||||||
|
_audioPlayer.PlayingSongChanged += OnPlayingSongChanged;
|
||||||
|
|
||||||
_playlists = new ObservableCollection<PlaylistItemViewModel>(_playlistManager.Playlists.Select(p => new PlaylistItemViewModel(p)));
|
_playlists = new ObservableCollection<PlaylistItemViewModel>(_playlistManager.Playlists.Select(p => new PlaylistItemViewModel(p)));
|
||||||
_selectedPlaylist = _playlists.FirstOrDefault(pv => pv.Playlist == _playlistManager.CurrentPlaylist);
|
_selectedPlaylist = _playlists.FirstOrDefault(pv => pv.Playlist == _playlistManager.CurrentPlaylist);
|
||||||
}
|
}
|
||||||
@@ -70,4 +87,9 @@ public partial class PlaylistsViewModel : ViewModelBase
|
|||||||
Playlists.Remove(playlistView);
|
Playlists.Remove(playlistView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPlayingSongChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ActivePlaylist = Playlists.FirstOrDefault(pv => pv.Playlist == _audioPlayer.Playlist);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,34 @@
|
|||||||
d:DataContext="{d:DesignInstance Type=vm:PlaylistsViewModel, IsDesignTimeCreatable=True}"
|
d:DataContext="{d:DesignInstance Type=vm:PlaylistsViewModel, IsDesignTimeCreatable=True}"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
|
<SolidColorBrush x:Key="PlaylistItemIconBrush" Color="#dddddd"/>
|
||||||
|
<SolidColorBrush x:Key="PlaylistItemTitleBrush" Color="#dddddd"/>
|
||||||
<SolidColorBrush x:Key="PlaylistItemSubtitleBrush" Color="#aaaaaa"/>
|
<SolidColorBrush x:Key="PlaylistItemSubtitleBrush" Color="#aaaaaa"/>
|
||||||
|
|
||||||
|
<!-- Playlist Icon Path -->
|
||||||
|
<Style x:Key="PlaylistIconPath" TargetType="PathIcon">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource PlaylistItemIconBrush}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="SelectedPlaylistIconPath" TargetType="PathIcon" BasedOn="{StaticResource PlaylistIconPath}">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AccentTextFillColorPrimaryBrush}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Playlist Title Text Block -->
|
||||||
|
<Style x:Key="PlaylistTitleTextBlock" TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource PlaylistItemTitleBrush}"/>
|
||||||
|
<Setter Property="FontSize" Value="15"/>
|
||||||
|
<Setter Property="FontWeight" Value="Medium"/>
|
||||||
|
<Setter Property="LineStackingStrategy" Value="BlockLineHeight"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
|
||||||
|
<Setter Property="LineHeight" Value="0"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="SelectedPlaylistTitleTextBlock" TargetType="TextBlock" BasedOn="{StaticResource PlaylistTitleTextBlock}">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AccentTextFillColorPrimaryBrush}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- Playlist Item Subtitle Text Block -->
|
<!-- Playlist Item Subtitle Text Block -->
|
||||||
<Style x:Key="PlaylistSubtitleTextBlock" TargetType="TextBlock">
|
<Style x:Key="PlaylistSubtitleTextBlock" TargetType="TextBlock">
|
||||||
<Setter Property="Foreground" Value="{StaticResource PlaylistItemSubtitleBrush}"/>
|
<Setter Property="Foreground" Value="{StaticResource PlaylistItemSubtitleBrush}"/>
|
||||||
@@ -23,26 +49,31 @@
|
|||||||
<Setter Property="LineHeight" Value="0"/>
|
<Setter Property="LineHeight" Value="0"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="SelectedPlaylistSubtitleTextBlock" TargetType="TextBlock" BasedOn="{StaticResource PlaylistSubtitleTextBlock}">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AccentTextFillColorSecondaryBrush}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<DataTemplate x:Key="PlaylistTemplate" x:DataType="vm:PlaylistItemViewModel">
|
<DataTemplate x:Key="PlaylistTemplate" x:DataType="vm:PlaylistItemViewModel">
|
||||||
<Grid>
|
<Grid x:Name="PlaylistListViewItem">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="200" />
|
<ColumnDefinition Width="200" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||||
<Viewbox Height="24" Width="24">
|
<Viewbox Height="24" Width="24">
|
||||||
<PathIcon Data="{StaticResource MusicNoteList}" />
|
<PathIcon x:Name="MusicNoteListPathIcon" Data="{StaticResource MusicNoteList}" />
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
<TextBlock Text="{x:Bind Name, Mode=OneWay}" FontSize="16" VerticalAlignment="Center" />
|
<TextBlock x:Name="PlaylistTitleTextBlock" Text="{x:Bind Name, Mode=OneWay}" Style="{StaticResource PlaylistTitleTextBlock}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Spacing="8">
|
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Spacing="8">
|
||||||
<TextBlock Text="{x:Bind Summary, Mode=OneWay}" Style="{StaticResource PlaylistSubtitleTextBlock}" />
|
<TextBlock x:Name="PlaylistSubtitleTextBlock" Text="{x:Bind Summary, Mode=OneWay}" Style="{StaticResource PlaylistSubtitleTextBlock}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<Grid Margin="10">
|
<Grid Margin="10">
|
||||||
<ListView
|
<ListView
|
||||||
|
Name="PlaylistsListView"
|
||||||
ItemsSource="{Binding Playlists, Mode=OneWay}"
|
ItemsSource="{Binding Playlists, Mode=OneWay}"
|
||||||
SelectedItem="{Binding SelectedPlaylist, Mode=TwoWay}"
|
SelectedItem="{Binding SelectedPlaylist, Mode=TwoWay}"
|
||||||
ItemTemplate="{StaticResource PlaylistTemplate}">
|
ItemTemplate="{StaticResource PlaylistTemplate}">
|
||||||
|
|||||||
@@ -1,12 +1,99 @@
|
|||||||
|
using CommunityToolkit.WinUI;
|
||||||
|
using Harmonia.WinUI.ViewModels;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace Harmonia.WinUI.Views;
|
namespace Harmonia.WinUI.Views;
|
||||||
|
|
||||||
public sealed partial class PlaylistsView : UserControl
|
public sealed partial class PlaylistsView : UserControl
|
||||||
{
|
{
|
||||||
|
private readonly PlaylistsViewModel _viewModel;
|
||||||
|
|
||||||
public PlaylistsView()
|
public PlaylistsView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
_viewModel = (PlaylistsViewModel)DataContext;
|
||||||
|
_viewModel.PropertyChanging += OnViewModelPropertyChanging;
|
||||||
|
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnViewModelPropertyChanging(object? sender, PropertyChangingEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.PropertyName)
|
||||||
|
{
|
||||||
|
case nameof(_viewModel.ActivePlaylist):
|
||||||
|
UpdateListViewItemStyle(PlaylistItemStyle.Normal);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.PropertyName)
|
||||||
|
{
|
||||||
|
case nameof(_viewModel.ActivePlaylist):
|
||||||
|
UpdateListViewItemStyle(PlaylistItemStyle.Selected);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateListViewItemStyle(PlaylistItemStyle style)
|
||||||
|
{
|
||||||
|
ListViewItem listViewItem = (ListViewItem)PlaylistsListView.ContainerFromItem(_viewModel.ActivePlaylist);
|
||||||
|
|
||||||
|
if (listViewItem is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
FrameworkElement? frameworkElement = TryGetFrameworkElement(listViewItem, "PlaylistListViewItem");
|
||||||
|
|
||||||
|
if (frameworkElement == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string playlistItemIconBrushName = style == PlaylistItemStyle.Selected ? "SelectedPlaylistIconPath" : "PlaylistIconPath";
|
||||||
|
string playlistItemTitleBrushName = style == PlaylistItemStyle.Selected ? "SelectedPlaylistTitleTextBlock" : "PlaylistTitleTextBlock";
|
||||||
|
string playlistItemSubtitleBrushName = style == PlaylistItemStyle.Selected ? "SelectedPlaylistSubtitleTextBlock" : "PlaylistSubtitleTextBlock";
|
||||||
|
|
||||||
|
UpdateElementStyle(frameworkElement, "MusicNoteListPathIcon", playlistItemIconBrushName);
|
||||||
|
UpdateElementStyle(frameworkElement, "PlaylistTitleTextBlock", playlistItemTitleBrushName);
|
||||||
|
UpdateElementStyle(frameworkElement, "PlaylistSubtitleTextBlock", playlistItemSubtitleBrushName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FrameworkElement? TryGetFrameworkElement(ListViewItem listViewItem, string elementName)
|
||||||
|
{
|
||||||
|
if (listViewItem.Name == elementName)
|
||||||
|
{
|
||||||
|
return listViewItem;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (listViewItem.FindDescendant(elementName) is not FrameworkElement frameworkElement)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return frameworkElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum PlaylistItemStyle
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
Selected
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user