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

@@ -11,6 +11,32 @@
<Application.Styles>
<!--<FluentTheme />-->
<semi:SemiTheme Locale="en-US"/>
<Style Selector="ContextMenu">
<!--<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform Y="-10" />
</Setter.Value>
</Setter>-->
<!--<Setter Property="Opacity" Value="1" />-->
<Setter Property="FontSize" Value="13"/>
<Style.Animations>
<Animation Duration="0.2">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="0" />
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/>
<!--<Setter Property="TranslateTransform.Y" Value="-100" />-->
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="1" />
<!--<Setter Property="TranslateTransform.Y" Value="0" />-->
<Setter Property="ScaleTransform.ScaleX" Value="1"/>
<Setter Property="ScaleTransform.ScaleY" Value="1"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Application.Styles>
<Application.Resources>
@@ -62,6 +88,20 @@
M11 4v1.466a.25.25 0 0 0 .41.192l2.36-1.966a.25.25 0 0 0 0-.384l-2.36-1.966a.25.25 0 0 0-.41.192V3H5a5 5 0 0 0-4.48 7.223.5.5 0 0 0 .896-.446A4 4 0 0 1 5 4zm4.48 1.777a.5.5 0 0 0-.896.446A4 4 0 0 1 11 12H5.001v-1.466a.25.25 0 0 0-.41-.192l-2.36 1.966a.25.25 0 0 0 0 .384l2.36 1.966a.25.25 0 0 0 .41-.192V13h6a5 5 0 0 0 4.48-7.223Z
M9 5.5a.5.5 0 0 0-.854-.354l-1.75 1.75a.5.5 0 1 0 .708.708L8 6.707V10.5a.5.5 0 0 0 1 0z
</StreamGeometry>
<StreamGeometry x:Key="DeleteIcon">
M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z
M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z
</StreamGeometry>
<StreamGeometry x:Key="CutIcon">
M3.5 3.5c-.614-.884-.074-1.962.858-2.5L8 7.226 11.642 1c.932.538 1.472 1.616.858 2.5L8.81 8.61l1.556 2.661a2.5 2.5 0 1 1-.794.637L8 9.73l-1.572 2.177a2.5 2.5 0 1 1-.794-.637L7.19 8.61zm2.5 10a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0m7 0a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0
</StreamGeometry>
<StreamGeometry x:Key="CopyIcon">
M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z
</StreamGeometry>
<StreamGeometry x:Key="PasteIcon">
M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1z
M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0z
</StreamGeometry>
</Application.Resources>
</Application>

View File

@@ -19,8 +19,9 @@ public partial class App : Application
{
ServiceCollection services = new();
services.AddSingleton<MainViewModel>();
services.AddSingleton<MainWindow>();
services.AddSingleton<MainViewModel>();
services.AddSingleton<PlaybackBarViewModel>();
services.AddSingleton<PlayingSongInfoViewModel>();
services.AddSingleton<PlaylistViewModel>();

View File

@@ -18,7 +18,7 @@
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
<PackageReference Include="Semi.Avalonia" Version="11.2.1.5" />
</ItemGroup>

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;
}
}

View File

@@ -6,6 +6,7 @@
xmlns:converter="clr-namespace:Harmonia.UI.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
DataContext="{x:Static vm:ViewModelLocator.PlaylistViewModel}"
Loaded="OnLoaded"
x:Class="Harmonia.UI.Views.PlaylistView"
x:DataType="vm:PlaylistViewModel">
<UserControl.Resources>
@@ -46,7 +47,12 @@
</UserControl.Styles>
<Grid Margin="0">
<Canvas Background="#99000000"></Canvas> <!-- Was 99 !-->
<ListBox ItemsSource="{Binding PlaylistSongs}" DoubleTapped="ListBox_DoubleTapped">
<ListBox
Name="PlaylistListBox"
ItemsSource="{Binding PlaylistSongs}"
SelectedItems="{Binding SelectedPlaylistSongs, Mode=OneWay}"
DoubleTapped="ListBox_DoubleTapped"
SelectionMode="Multiple">
<!--<ListBox.Styles>
<Style Selector="ListBoxItem:nth-child(odd):not(:pointerover):not(:selected)">
<Setter Property="Background" Value="#00000000"/>
@@ -87,6 +93,86 @@
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ContextMenu>
<ContextMenu Name="PlaylistContextMenu" Padding="10" Background="#EE444444">
<MenuItem Command="{Binding PlaySongCommand}" HotKey="Enter" InputGesture="Enter">
<MenuItem.Header>
<TextBlock Text="Play" FontWeight="Bold" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource SemiIconPlay}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Command="{Binding AddFilesCommand}" HotKey="Insert" InputGesture="Insert">
<MenuItem.Header>
<TextBlock Text="Add Files..." Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource SemiIconFile}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding AddFolderCommand}" HotKey="Ctrl+Insert" InputGesture="Ctrl+Insert">
<MenuItem.Header>
<TextBlock Text="Add Folder..." Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource SemiIconFolder}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Command="{Binding RemoveSongsCommand}" HotKey="Delete" InputGesture="Delete">
<MenuItem.Styles>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="#ff99a4"/>
</Style>
</MenuItem.Styles>
<MenuItem.Header>
<TextBlock Text="Remove" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource DeleteIcon}" Foreground="#ff99a4"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Command="{Binding CutSongsCommand}" HotKey="Ctrl+X" InputGesture="Ctrl+X">
<MenuItem.Header>
<TextBlock Text="Cut" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource CutIcon}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding CopySongsCommand}" HotKey="Ctrl+C" InputGesture="Ctrl+C">
<MenuItem.Header>
<TextBlock Text="Copy" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource CopyIcon}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding PasteSongsCommand}" HotKey="Ctrl+V" InputGesture="Ctrl+V">
<MenuItem.Header>
<TextBlock Text="Paste" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource PasteIcon}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Command="{Binding OpenFileLocationCommand}" HotKey="Alt+O" InputGesture="Alt+O">
<MenuItem.Header>
<TextBlock Text="Open File Location" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource SemiIconFolderOpen}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.KeyBindings>
<KeyBinding Gesture="Enter" Command="{Binding PlaySongCommand}" />
</ListBox.KeyBindings>
</ListBox>
</Grid>

View File

@@ -2,15 +2,17 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Harmonia.Core.Caching;
using Harmonia.Core.Imaging;
using Avalonia.VisualTree;
using Harmonia.Core.Engine;
using Harmonia.Core.Playlists;
using Harmonia.UI.ViewModels;
using Microsoft.Extensions.Caching.Memory;
using System;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -19,19 +21,29 @@ namespace Harmonia.UI.Views;
public partial class PlaylistView : UserControl
{
private readonly PlaylistViewModel _viewModel;
private readonly IAudioEngine _audioEngine;
private readonly ConcurrentDictionary<int, CancellationTokenSource> _imageCancellationTokens = [];
private IStorageProvider? _storageProvider;
public PlaylistView()
{
InitializeComponent();
_viewModel = (PlaylistViewModel)DataContext!;
_audioEngine = App.ServiceProvider.GetRequiredService<IAudioEngine>();
}
private void ListBox_DoubleTapped(object? sender, TappedEventArgs e)
private void OnLoaded(object? sender, RoutedEventArgs e)
{
_storageProvider = TopLevel.GetTopLevel(this)?.StorageProvider;
}
private async void ListBox_DoubleTapped(object? sender, TappedEventArgs e)
{
if (sender is ListBox listBox && listBox.SelectedItem is PlaylistSong playlistSong)
{
_viewModel.PlaySong(playlistSong);
await _viewModel.PlaySongAsync(playlistSong);
}
}
@@ -88,4 +100,49 @@ public partial class PlaylistView : UserControl
_imageCancellationTokens.TryGetValue(hashCode, out CancellationTokenSource? cancellationTokenSource);
cancellationTokenSource?.Cancel();
}
//private async void AddFiles_Click(object? sender, RoutedEventArgs e)
//{
// 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)];
// CancellationToken cancellationToken = default;
// await _viewModel.AddFilesAsync(fileNames, cancellationToken);
//}
//private FilePickerFileType GetAudioFileTypes()
//{
// return new("Audo Files")
// {
// Patterns = [.. _audioEngine.SupportedFormats]
// };
//}
//private async void AddFolder_Click(object? sender, RoutedEventArgs e)
//{
// if (_storageProvider == null)
// return;
// FolderPickerOpenOptions options = new()
// {
// AllowMultiple = true
// };
// IReadOnlyList<IStorageFolder> folders = await _storageProvider.OpenFolderPickerAsync(options);
// if (folders.Count == 0)
// return;
// CancellationToken cancellationToken = default;
// await _viewModel.AddFolderAsync(folders[0].TryGetLocalPath() ?? string.Empty, cancellationToken);
//}
}