Moved playlist commands to view model.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user