Moved playlist commands to view model.

This commit is contained in:
2025-03-14 22:59:15 -04:00
parent bd9b30abbd
commit 7c70eb3814
9 changed files with 565 additions and 23 deletions

View File

@@ -7,6 +7,24 @@ using System.Threading;
using Avalonia.Media.Imaging;
using System.Collections.Concurrent;
using Harmonia.UI.Caching;
using Harmonia.Core.Scanner;
using Harmonia.Core.Models;
using Avalonia.Threading;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls;
using Avalonia.Input.Platform;
using Avalonia.VisualTree;
using Avalonia.Rendering;
using System.Diagnostics;
using Avalonia.Platform.Storage;
using Harmonia.Core.Engine;
using Avalonia.Input;
namespace Harmonia.UI.ViewModels;
@@ -14,8 +32,11 @@ public class PlaylistViewModel : ViewModelBase
{
private readonly IAudioPlayer _audioPlayer;
private readonly IAudioBitmapCache _audioBitmapImageCache;
private readonly IAudioFileScanner _audioFileScanner;
private readonly IAudioEngine _audioEngine;
private readonly ConcurrentDictionary<string, Bitmap> _bitmapDictionary = [];
public Playlist? Playlist { get; private set; }
public PlaylistSong? PlayingSong => _audioPlayer.PlayingSong;
private ObservableCollection<PlaylistSong> _playlistSongs = [];
@@ -32,34 +53,371 @@ public class PlaylistViewModel : ViewModelBase
}
}
public PlaylistViewModel(IAudioPlayer audioPlayer, IAudioBitmapCache audioBitmapImageCache)
private ObservableCollection<PlaylistSong> _selectedPlaylistSongs = [];
public ObservableCollection<PlaylistSong> SelectedPlaylistSongs
{
get
{
return _selectedPlaylistSongs;
}
set
{
_selectedPlaylistSongs = value;
OnPropertyChanged();
}
}
public ICommand PlaySongCommand => new AsyncRelayCommand(PlaySongAsync, AreSongsSelected);
public ICommand AddFilesCommand => new AsyncRelayCommand(AddFilesAsync);
public ICommand AddFolderCommand => new AsyncRelayCommand(AddFolderAsync);
public ICommand RemoveSongsCommand => new RelayCommand(RemoveSongs, AreSongsSelected);
public ICommand CutSongsCommand => new AsyncRelayCommand(CutSongsAsync, AreSongsSelected);
public ICommand CopySongsCommand => new AsyncRelayCommand(CopySongsAsync, AreSongsSelected);
public ICommand PasteSongsCommand => new AsyncRelayCommand(PasteSongsAsync, CanPasteSongs);
public ICommand OpenFileLocationCommand => new RelayCommand(OpenFileLocation, AreSongsSelected);
public PlaylistViewModel(IAudioPlayer audioPlayer, IAudioBitmapCache audioBitmapImageCache, IAudioFileScanner audioFileScanner, IAudioEngine audioEngine)
{
_audioPlayer = audioPlayer;
_audioPlayer.PlaylistChanged += OnAudioPlayerPlaylistChanged;
_audioPlayer.PlayingSongChanged += OnAudioPlayerPlayingSongChanged;
_audioBitmapImageCache = audioBitmapImageCache;
_audioFileScanner = audioFileScanner;
_audioEngine = audioEngine;
}
private void OnAudioPlayerPlaylistChanged(object? sender, EventArgs e)
{
if (Playlist != null)
{
Playlist.PlaylistUpdated -= OnPlaylistUpdated;
}
Playlist = _audioPlayer.Playlist;
if (Playlist != null)
{
Playlist.PlaylistUpdated += OnPlaylistUpdated;
}
PlaylistSong[] playlistSongs = _audioPlayer.Playlist?.Songs.ToArray() ?? [];
PlaylistSongs = [.. playlistSongs];
}
private void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e)
{
switch (e.Action)
{
case PlaylistUpdateAction.Add:
Dispatcher.UIThread.Invoke(() => AddSongs(e.Songs, e.Index));
break;
case PlaylistUpdateAction.Remove:
Dispatcher.UIThread.Invoke(() => RemoveSongsFromCollection(e.Songs));
break;
}
}
private void AddSongs(PlaylistSong[] playlistSongs, int index = 0)
{
// TODO: Performance improvements
int currentIndex = index;
foreach (PlaylistSong playlistSong in playlistSongs)
{
PlaylistSongs.Insert(currentIndex++, playlistSong);
}
}
private void RemoveSongsFromCollection(PlaylistSong[] playlistSongs)
{
foreach (PlaylistSong playlistSong in playlistSongs)
{
PlaylistSongs.Remove(playlistSong);
}
}
private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e)
{
OnPropertyChanged(nameof(PlayingSong));
}
public void PlaySong(PlaylistSong playlistSong)
public async Task PlaySongAsync(PlaylistSong playlistSong)
{
_audioPlayer.LoadAsync(playlistSong, PlaybackMode.LoadAndPlay);
await _audioPlayer.LoadAsync(playlistSong, PlaybackMode.LoadAndPlay);
}
public async Task<Bitmap?> GetBitmapAsync(PlaylistSong playlistSong, CancellationToken cancellationToken)
{
return await _audioBitmapImageCache.GetAsync(playlistSong.Song, cancellationToken);
}
#region Commands
private async Task PlaySongAsync()
{
if (SelectedPlaylistSongs.Count == 0)
return;
await _audioPlayer.LoadAsync(SelectedPlaylistSongs[0], PlaybackMode.LoadAndPlay);
}
private bool AreSongsSelected()
{
return SelectedPlaylistSongs.Count > 0;
}
private async Task AddFilesAsync(CancellationToken cancellationToken)
{
if (Playlist == null)
return;
IStorageProvider? storageProvider = StorageProvider.Get();
if (storageProvider == null)
return;
FilePickerOpenOptions openOptions = new()
{
FileTypeFilter = [GetAudioFileTypes()],
AllowMultiple = true
};
IReadOnlyList<IStorageFile> result = await storageProvider.OpenFilePickerAsync(openOptions);
string[] fileNames = [.. result.Select(file => file.TryGetLocalPath() ?? string.Empty)];
Song[] songs = await _audioFileScanner.GetSongsAsync(fileNames, cancellationToken);
Playlist.AddSongs(songs);
}
private FilePickerFileType GetAudioFileTypes()
{
return new("Audo Files")
{
Patterns = [.. _audioEngine.SupportedFormats]
};
}
private async Task AddFolderAsync(CancellationToken cancellationToken)
{
if (Playlist == null)
return;
IStorageProvider? storageProvider = StorageProvider.Get();
if (storageProvider == null)
return;
FolderPickerOpenOptions options = new()
{
AllowMultiple = true
};
IReadOnlyList<IStorageFolder> folders = await storageProvider.OpenFolderPickerAsync(options);
if (folders.Count == 0)
return;
string? path = folders[0].TryGetLocalPath();
if (string.IsNullOrWhiteSpace(path))
return;
Song[] songs = await _audioFileScanner.GetSongsFromPathAsync(path, cancellationToken);
Playlist.AddSongs(songs);
}
public async Task AddFilesAsync(string[] fileNames, CancellationToken cancellationToken)
{
if (Playlist == null)
return;
Song[] songs = await _audioFileScanner.GetSongsAsync(fileNames, cancellationToken);
Playlist.AddSongs(songs);
}
public async Task AddFolderAsync(string path, CancellationToken cancellationToken)
{
if (Playlist == null)
return;
Song[] songs = await _audioFileScanner.GetSongsFromPathAsync(path, cancellationToken);
Playlist.AddSongs(songs);
}
private void RemoveSongs()
{
if (Playlist == null)
return;
if (SelectedPlaylistSongs.Count == 0)
return;
PlaylistSong[] playlistSongs = [.. SelectedPlaylistSongs];
Playlist.RemoveSongs(playlistSongs);
}
private async Task CutSongsAsync()
{
if (SelectedPlaylistSongs.Count == 0)
return;
await CopySelectedSongsToClipboardAsync();
}
private async Task CopySongsAsync()
{
if (SelectedPlaylistSongs.Count == 0)
return;
await CopySelectedSongsToClipboardAsync();
}
private async Task CopySelectedSongsToClipboardAsync()
{
IClipboard? clipboard = Clipboard.Get();
if (clipboard == null)
return;
Song[] songs = [.. SelectedPlaylistSongs.Select(playlistSong => playlistSong.Song)];
await clipboard.SetTextAsync(JsonSerializer.Serialize(songs));
}
//private async Task CopySelectedSongsToClipboard2Async()
//{
// IClipboard? clipboard = Clipboard.Get();
// if (clipboard == null)
// return;
// Song[] songs = [.. SelectedPlaylistSongs.Select(playlistSong => playlistSong.Song)];
// DataObject dataObject = new();
// dataObject.Set(DataFormats.Text, JsonSerializer.Serialize(songs));
// await clipboard.SetDataObjectAsync(dataObject);
//}
private bool CanPasteSongs()
{
if (Playlist == null)
return false;
IClipboard? clipboard = Clipboard.Get();
if (clipboard == null)
return false;
string? clipboardText = clipboard.GetTextAsync().Result;
if (string.IsNullOrWhiteSpace(clipboardText))
return false;
Song[] songs = [];
try
{
songs = JsonSerializer.Deserialize<Song[]>(clipboardText) ?? [];
}
catch (JsonException)
{
return false;
}
return songs.Length > 0;
}
private async Task PasteSongsAsync()
{
if (Playlist == null)
return;
IClipboard? clipboard = Clipboard.Get();
if (clipboard == null)
return;
string? clipboardText = await clipboard.GetTextAsync();
if (string.IsNullOrWhiteSpace(clipboardText))
return;
Song[] songs = [];
try
{
songs = JsonSerializer.Deserialize<Song[]>(clipboardText) ?? [];
}
catch (JsonException)
{
}
Playlist.AddSongs(songs);
}
private void OpenFileLocation()
{
if (SelectedPlaylistSongs.Count == 0)
return;
string argument = "/select, \"" + SelectedPlaylistSongs[0].Song.FileName + "\"";
Process.Start("explorer.exe", argument);
}
#endregion
}
public class Clipboard
{
public static IClipboard? Get()
{
//Desktop
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
return desktop.MainWindow?.Clipboard;
}
//Android (and iOS?)
else if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
IRenderRoot? visualRoot = singleViewPlatform.MainView?.GetVisualRoot();
if (visualRoot is TopLevel topLevel)
{
return topLevel.Clipboard;
}
}
return null;
}
}
public class StorageProvider
{
public static IStorageProvider? Get()
{
//Desktop
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
return desktop.MainWindow?.StorageProvider;
}
//Android (and iOS?)
else if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
IRenderRoot? visualRoot = singleViewPlatform.MainView?.GetVisualRoot();
if (visualRoot is TopLevel topLevel)
{
return topLevel.StorageProvider;
}
}
return null;
}
}