Moved playlist commands to view model.
This commit is contained in:
@@ -95,11 +95,11 @@ public class BassAudioEngine : IAudioEngine, IDisposable
|
||||
_mediaPlayer.PropertyChanged += OnMediaPlayerPropertyChanged;
|
||||
|
||||
List<string> supportedFormats = [.. Bass.SupportedFormats.Split(';')];
|
||||
supportedFormats.Add(".aac");
|
||||
supportedFormats.Add(".m4a");
|
||||
supportedFormats.Add(".flac");
|
||||
supportedFormats.Add(".opus");
|
||||
supportedFormats.Add(".wma");
|
||||
//supportedFormats.Add(".aac");
|
||||
//supportedFormats.Add(".m4a");
|
||||
supportedFormats.Add("*.flac");
|
||||
//supportedFormats.Add(".opus");
|
||||
//supportedFormats.Add(".wma");
|
||||
|
||||
SupportedFormats = [.. supportedFormats];
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ManagedBass" Version="3.1.1" />
|
||||
<PackageReference Include="ManagedBass.Flac" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.3" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace Harmonia.Core.Playlists;
|
||||
|
||||
public class PlaylistSong(Song song)
|
||||
{
|
||||
public string UID { get; } = Guid.NewGuid().ToString();
|
||||
public string UID { get; init; } = Guid.NewGuid().ToString();
|
||||
public Song Song { get; init; } = song;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
//}
|
||||
}
|
||||
Reference in New Issue
Block a user