Various updates.

This commit is contained in:
2026-01-25 17:17:31 -05:00
parent f459e0e6e6
commit e1338563ed
15 changed files with 307 additions and 32 deletions

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
namespace Harmonia.Core.Caching; namespace Harmonia.Core.Caching;
@@ -27,10 +28,16 @@ public abstract class MemoryCache<TKey, TValue> : Cache<TKey, TValue> where TKey
var cacheEntryOptions = new MemoryCacheEntryOptions() var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSize(entrySize) .SetSize(entrySize)
.SetSlidingExpiration(SlidingExpiration); .SetSlidingExpiration(SlidingExpiration)
.RegisterPostEvictionCallback(PostEvictionCallback);
_memoryCache.Set(key, entry, cacheEntryOptions); _memoryCache.Set(key, entry, cacheEntryOptions);
} }
protected virtual void PostEvictionCallback(object? cacheKey, object? cacheValue, EvictionReason evictionReason, object? state)
{
}
protected abstract long GetEntrySize(TValue entry); protected abstract long GetEntrySize(TValue entry);
} }

View File

@@ -7,12 +7,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ManagedBass" Version="3.1.1" /> <PackageReference Include="ManagedBass" Version="4.0.2" />
<PackageReference Include="ManagedBass.Flac" Version="3.1.1" /> <PackageReference Include="ManagedBass.Flac" Version="4.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="TagLibSharp" Version="2.3.0" /> <PackageReference Include="TagLibSharp" Version="2.3.0" />
</ItemGroup> </ItemGroup>

View File

@@ -23,4 +23,26 @@ public class Song
public string? FileDirectory => Directory.GetParent(FileName)?.Name; public string? FileDirectory => Directory.GetParent(FileName)?.Name;
public string? FileType => Path.GetExtension(FileName)?.Replace(".", "").ToUpper(); public string? FileType => Path.GetExtension(FileName)?.Replace(".", "").ToUpper();
public string ShortFileName => Path.GetFileNameWithoutExtension(FileName); public string ShortFileName => Path.GetFileNameWithoutExtension(FileName);
public void Update(Song song)
{
if (string.Equals(song.FileName, FileName, StringComparison.OrdinalIgnoreCase) == false)
return;
Size = song.Size;
LastModified = song.LastModified;
Title = song.Title;
Album = song.Album;
Artists = song.Artists;
AlbumArtists = song.AlbumArtists;
DiscNumber = song.DiscNumber;
TrackNumber = song.TrackNumber;
Length = song.Length;
Year = song.Year;
Genre = song.Genre;
BitRate = song.BitRate;
SampleRate = song.SampleRate;
ImageName = song?.ImageName;
ImageHash = song?.ImageHash;
}
} }

View File

@@ -172,6 +172,7 @@ public class Playlist
foreach (PlaylistSong playlistSong in playlistSongs) foreach (PlaylistSong playlistSong in playlistSongs)
{ {
//playlistSong.Song = song; //playlistSong.Song = song;
//playlistSong.Song.Update(song);
} }
} }
} }

View File

@@ -16,14 +16,14 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" /> <PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="Shouldly" Version="4.3.0" /> <PackageReference Include="Shouldly" Version="4.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2"> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="xunit.v3" Version="2.0.0" /> <PackageReference Include="xunit.v3" Version="3.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -10,7 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.Desktop" Version="11.2.6" /> <PackageReference Include="Avalonia.Desktop" Version="11.3.7" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -11,15 +11,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.6" /> <PackageReference Include="Avalonia" Version="11.3.7" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.6" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.7" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.6" /> <PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.7" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--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.6" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
<PackageReference Include="Semi.Avalonia" Version="11.2.1.6" /> <PackageReference Include="Semi.Avalonia" Version="11.3.7" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -2,8 +2,10 @@
using Harmonia.Core.Imaging; using Harmonia.Core.Imaging;
using Harmonia.Core.Models; using Harmonia.Core.Models;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Media.Imaging;
using System; using System;
using System.Collections.Concurrent;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -100,3 +102,127 @@ public class AudioBitmapImageCache(IAudioImageExtractor audioImageExtractor) : M
return entry.DecodePixelWidth * entry.DecodePixelHeight; return entry.DecodePixelWidth * entry.DecodePixelHeight;
} }
} }
public class AudioBitmapImageCache2 : MemoryCache<Song, BitmapImage>, IAudioBitmapImageCache
{
private readonly IAudioImageExtractor _audioImageExtractor;
private readonly BitmapImage[] _bitmapPool;
private int _nextIndex = 0;
private ConcurrentDictionary<object, BitmapImage> _test = [];
protected override MemoryCacheOptions Options => new()
{
SizeLimit = 200_000_000,
CompactionPercentage = 0.2,
};
protected override TimeSpan SlidingExpiration => TimeSpan.FromSeconds(600);
protected override int MaxConcurrentRequests => 8;
protected virtual int MaxImageWidthOrHeight => 1000;
protected virtual int BitmapPoolSize => 64;
public AudioBitmapImageCache2(IAudioImageExtractor audioImageExtractor)
{
_audioImageExtractor = audioImageExtractor;
_bitmapPool = new BitmapImage[BitmapPoolSize];
InitializeBitmapPool();
}
private void InitializeBitmapPool()
{
for (int i = 0; i < _bitmapPool.Length; i++)
_bitmapPool[i] = new BitmapImage();
//DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread();
//TaskCompletionSource taskCompletionSource = new();
//dispatcherQueue.TryEnqueue(() =>
//{
// for (int i = 0; i < _bitmapPool.Length; i++)
// _bitmapPool[i] = new BitmapImage();
// taskCompletionSource.SetResult();
//});
//taskCompletionSource.Task.GetAwaiter().GetResult();
}
protected override object? GetKey(Song key)
{
if (string.IsNullOrWhiteSpace(key.ImageHash) == false)
{
return key.ImageHash;
}
else if (string.IsNullOrWhiteSpace(key.ImageName) == false)
{
return key.ImageName;
}
return "Default";
}
protected override async ValueTask<BitmapImage?> FetchAsync(Song key, CancellationToken cancellationToken)
{
int index = Interlocked.Increment(ref _nextIndex);
BitmapImage bitmapImage = _bitmapPool[index % _bitmapPool.Length];
//_test.AddOrUpdate(index, bitmapImage);
SongPictureInfo? songPictureInfo = await _audioImageExtractor.ExtractImageAsync(key.FileName, cancellationToken);
if (songPictureInfo == null)
{
bitmapImage.UriSource = new("ms-appx:///Assets/Default.png", UriKind.Absolute);
}
else
{
using MemoryStream stream = new(songPictureInfo.Data);
await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream());
}
bitmapImage.DecodePixelWidth = GetDecodePixelWidth(bitmapImage);
bitmapImage.DecodePixelHeight = GetDecodePixelHeight(bitmapImage);
return bitmapImage;
}
private int GetDecodePixelWidth(BitmapImage bitmapImage)
{
int originalImageWidth = bitmapImage.PixelWidth;
int orignalImageHeight = bitmapImage.PixelHeight;
if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight)
return 0;
if (orignalImageHeight > originalImageWidth)
return 0;
return MaxImageWidthOrHeight;
}
private int GetDecodePixelHeight(BitmapImage bitmapImage)
{
int originalImageWidth = bitmapImage.PixelWidth;
int orignalImageHeight = bitmapImage.PixelHeight;
if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight)
return 0;
if (originalImageWidth > orignalImageHeight)
return 0;
return MaxImageWidthOrHeight;
}
protected override long GetEntrySize(BitmapImage entry)
{
return entry.DecodePixelWidth * entry.DecodePixelHeight;
}
protected override void PostEvictionCallback(object? cacheKey, object? cacheValue, EvictionReason evictionReason, object? state)
{
}
}

View File

@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Harmonia.WinUI.Flex;
public interface IFlexView
{
FlexLayout Layout { get; }
int ImageWidth { get; }
FlexOrientation ListOrientation { get; }
int RowSpacing { get; }
int ColumnSpacing { get; }
int TextLineHeight { get; }
FlexOrientation SubtitleFooterOrientation { get; }
int SubtitleFooterMargin { get; }
FlexOrientation ImageToTextOrientation { get; }
int TitleFontSize { get; }
int SubtitleFontSize { get; }
int FooterFontSize { get; }
}
public abstract class GridFlexViewBase : IFlexView
{
public FlexLayout Layout => FlexLayout.Grid;
public FlexOrientation ListOrientation => FlexOrientation.Horizontal;
public FlexOrientation SubtitleFooterOrientation => FlexOrientation.Vertical;
public FlexOrientation ImageToTextOrientation => FlexOrientation.Vertical;
public abstract int ImageWidth { get; }
public abstract int RowSpacing { get; }
public abstract int ColumnSpacing { get; }
public abstract int TextLineHeight { get; }
public abstract int SubtitleFooterMargin { get; }
public abstract int TitleFontSize { get; }
public abstract int SubtitleFontSize { get; }
public abstract int FooterFontSize { get; }
}
public abstract class ListFlexViewBase : IFlexView
{
public FlexLayout Layout => FlexLayout.List;
public FlexOrientation ListOrientation => FlexOrientation.Vertical;
public FlexOrientation SubtitleFooterOrientation => FlexOrientation.Vertical;
public FlexOrientation ImageToTextOrientation => FlexOrientation.Vertical;
public abstract int ImageWidth { get; }
public abstract int RowSpacing { get; }
public abstract int ColumnSpacing { get; }
public abstract int TextLineHeight { get; }
public abstract int SubtitleFooterMargin { get; }
public abstract int TitleFontSize { get; }
public abstract int SubtitleFontSize { get; }
public abstract int FooterFontSize { get; }
}
public enum FlexLayout
{
List,
Grid
}
public enum FlexOrientation
{
Horizontal,
Vertical
}

View File

@@ -43,9 +43,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.1.240916" /> <PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250310001" /> <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.250916003" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Harmonia.Core\Harmonia.Core.csproj" /> <ProjectReference Include="..\Harmonia.Core\Harmonia.Core.csproj" />

View File

@@ -5,19 +5,19 @@
<!-- Global Font --> <!-- Global Font -->
<Style TargetType="TextBlock"> <Style TargetType="TextBlock">
<Setter Property="FontFamily" Value="Lexend" /> <Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
</Style> </Style>
<Style TargetType="TextBox" BasedOn="{StaticResource DefaultTextBoxStyle}"> <Style TargetType="TextBox" BasedOn="{StaticResource DefaultTextBoxStyle}">
<Setter Property="FontFamily" Value="Lexend" /> <Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
</Style> </Style>
<Style TargetType="MenuFlyoutItem" BasedOn="{StaticResource DefaultMenuFlyoutItemStyle}"> <Style TargetType="MenuFlyoutItem" BasedOn="{StaticResource DefaultMenuFlyoutItemStyle}">
<Setter Property="FontFamily" Value="Lexend" /> <Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
</Style> </Style>
<Style TargetType="ListViewItem" BasedOn="{StaticResource DefaultListViewItemStyle}"> <Style TargetType="ListViewItem" BasedOn="{StaticResource DefaultListViewItemStyle}">
<Setter Property="FontFamily" Value="Lexend" /> <Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
</Style> </Style>
<!-- Flat Button --> <!-- Flat Button -->

View File

@@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Harmonia.WinUI.ViewModels; namespace Harmonia.WinUI.ViewModels;
public class PlayingSongViewModel : ViewModelBase public partial class PlayingSongViewModel : ViewModelBase
{ {
private readonly IAudioPlayer _audioPlayer; private readonly IAudioPlayer _audioPlayer;
private readonly IAudioBitmapImageCache _audioBitmapImageCache; private readonly IAudioBitmapImageCache _audioBitmapImageCache;

View File

@@ -1,5 +1,7 @@
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Harmonia.Core.Caching;
using Harmonia.Core.Engine; using Harmonia.Core.Engine;
using Harmonia.Core.Imaging;
using Harmonia.Core.Models; using Harmonia.Core.Models;
using Harmonia.Core.Player; using Harmonia.Core.Player;
using Harmonia.Core.Playlists; using Harmonia.Core.Playlists;
@@ -8,10 +10,10 @@ using Harmonia.WinUI.Caching;
using Harmonia.WinUI.Storage; using Harmonia.WinUI.Storage;
using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Media.Imaging;
using System; using System;
using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -20,7 +22,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers; using System.Timers;
using System.Windows.Input; using System.Windows.Input;
using Windows.ApplicationModel.Contacts;
using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer;
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue; using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
@@ -30,6 +31,7 @@ namespace Harmonia.WinUI.ViewModels;
public partial class PlaylistViewModel : ViewModelBase public partial class PlaylistViewModel : ViewModelBase
{ {
private readonly IAudioPlayer _audioPlayer; private readonly IAudioPlayer _audioPlayer;
private readonly IAudioImageCache _audioImageCache;
private readonly IAudioBitmapImageCache _audioBitmapImageCache; private readonly IAudioBitmapImageCache _audioBitmapImageCache;
private readonly IAudioFileScanner _audioFileScanner; private readonly IAudioFileScanner _audioFileScanner;
private readonly IAudioEngine _audioEngine; private readonly IAudioEngine _audioEngine;
@@ -126,6 +128,7 @@ public partial class PlaylistViewModel : ViewModelBase
public PlaylistViewModel( public PlaylistViewModel(
IAudioPlayer audioPlayer, IAudioPlayer audioPlayer,
IAudioImageCache audioImageCache,
IAudioBitmapImageCache audioBitmapImageCache, IAudioBitmapImageCache audioBitmapImageCache,
IAudioFileScanner audioFileScanner, IAudioFileScanner audioFileScanner,
IAudioEngine audioEngine, IAudioEngine audioEngine,
@@ -136,16 +139,27 @@ public partial class PlaylistViewModel : ViewModelBase
_audioPlayer.PlaylistChanged += OnPlaylistChanged; _audioPlayer.PlaylistChanged += OnPlaylistChanged;
_audioPlayer.PlayingSongChanged += OnPlayingSongChanged; _audioPlayer.PlayingSongChanged += OnPlayingSongChanged;
_audioImageCache = audioImageCache;
_audioBitmapImageCache = audioBitmapImageCache; _audioBitmapImageCache = audioBitmapImageCache;
_audioFileScanner = audioFileScanner; _audioFileScanner = audioFileScanner;
_audioEngine = audioEngine; _audioEngine = audioEngine;
_storageProvider = storageProvider; _storageProvider = storageProvider;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread(); _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
FilteredPlaylistSongs.CollectionChanged += OnFilteredPlaylistSongsCollectionChanged;
// Testing // Testing
Task.Run(() => PlayDemoSong(playlistRepository)); Task.Run(() => PlayDemoSong(playlistRepository));
} }
private void OnFilteredPlaylistSongsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (IsUserUpdating == false)
return;
int x = 1;
}
private async Task PlayDemoSong(IPlaylistRepository playlistRepository) private async Task PlayDemoSong(IPlaylistRepository playlistRepository)
{ {
if (playlistRepository.Get().Count == 0) if (playlistRepository.Get().Count == 0)
@@ -238,13 +252,24 @@ public partial class PlaylistViewModel : ViewModelBase
await _audioPlayer.LoadAsync(playlistSong, PlaybackMode.LoadAndPlay); await _audioPlayer.LoadAsync(playlistSong, PlaybackMode.LoadAndPlay);
} }
public async Task<SongPictureInfo?> GetSongPictureInfoAsync(int hashCode, PlaylistSong playlistSong)
{
_imageCancellationTokens.TryGetValue(hashCode, out CancellationTokenSource? cancellationTokenSource);
cancellationTokenSource?.Cancel();
cancellationTokenSource = new();
_imageCancellationTokens.AddOrUpdate(hashCode, cancellationTokenSource, (_, _) => cancellationTokenSource);
return await _audioImageCache.GetAsync(playlistSong.Song, cancellationTokenSource.Token);
}
public async Task<BitmapImage?> GetBitmapImageAsync(int hashCode, PlaylistSong playlistSong) public async Task<BitmapImage?> GetBitmapImageAsync(int hashCode, PlaylistSong playlistSong)
{ {
_imageCancellationTokens.TryGetValue(hashCode, out CancellationTokenSource? cancellationTokenSource); _imageCancellationTokens.TryGetValue(hashCode, out CancellationTokenSource? cancellationTokenSource);
cancellationTokenSource?.Cancel(); cancellationTokenSource?.Cancel();
cancellationTokenSource = new(); cancellationTokenSource = new();
_imageCancellationTokens.AddOrUpdate(hashCode, cancellationTokenSource, (_,_) => cancellationTokenSource); _imageCancellationTokens.AddOrUpdate(hashCode, cancellationTokenSource, (_, _) => cancellationTokenSource);
return await _audioBitmapImageCache.GetAsync(playlistSong.Song, cancellationTokenSource.Token); return await _audioBitmapImageCache.GetAsync(playlistSong.Song, cancellationTokenSource.Token);
} }
@@ -290,7 +315,7 @@ public partial class PlaylistViewModel : ViewModelBase
List<PlaylistSong> filteredPlaylistSongs = [.. Playlist.Songs.Where(playlistSong => IsFiltered(playlistSong.Song))]; List<PlaylistSong> filteredPlaylistSongs = [.. Playlist.Songs.Where(playlistSong => IsFiltered(playlistSong.Song))];
//FilteredPlaylistSongs = [.. filteredPlaylistSongs]; //FilteredPlaylistSongs = [.. filteredPlaylistSongs];
for (int i = FilteredPlaylistSongs.Count -1; i >= 0; i--) for (int i = FilteredPlaylistSongs.Count - 1; i >= 0; i--)
{ {
PlaylistSong playlistSong = FilteredPlaylistSongs[i]; PlaylistSong playlistSong = FilteredPlaylistSongs[i];
@@ -382,7 +407,7 @@ public partial class PlaylistViewModel : ViewModelBase
private FilePickerFileType GetAudioFileTypes() private FilePickerFileType GetAudioFileTypes()
{ {
string[] patterns = _audioEngine.SupportedFormats.Select(format => format.Replace("*", "")).ToArray(); string[] patterns = [.. _audioEngine.SupportedFormats.Select(format => format.Replace("*", ""))];
return new() return new()
{ {
@@ -396,7 +421,7 @@ public partial class PlaylistViewModel : ViewModelBase
if (Playlist == null) if (Playlist == null)
return; return;
string? path = await _storageProvider.GetPathAsync(); string? path = await _storageProvider.GetPathAsync();
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path))
return; return;

View File

@@ -184,6 +184,11 @@
Name="PlaylistListView" Name="PlaylistListView"
ItemsSource="{Binding FilteredPlaylistSongs}" ItemsSource="{Binding FilteredPlaylistSongs}"
ItemTemplate="{StaticResource SongTemplate}" ItemTemplate="{StaticResource SongTemplate}"
CanReorderItems="True"
CanDragItems="True"
DragItemsStarting="PlaylistListView_DragItemsStarting"
DragItemsCompleted="PlaylistListView_DragItemsCompleted"
AllowDrop="True"
SelectionMode="Extended" SelectionMode="Extended"
SelectionChanged="PlaylistListView_SelectionChanged"> SelectionChanged="PlaylistListView_SelectionChanged">
<ListView.ContextFlyout> <ListView.ContextFlyout>

View File

@@ -1,4 +1,5 @@
using CommunityToolkit.WinUI; using CommunityToolkit.WinUI;
using Harmonia.Core.Imaging;
using Harmonia.Core.Playlists; using Harmonia.Core.Playlists;
using Harmonia.WinUI.ViewModels; using Harmonia.WinUI.ViewModels;
using Microsoft.UI.Dispatching; using Microsoft.UI.Dispatching;
@@ -12,6 +13,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Windows.UI.Popups; using Windows.UI.Popups;
namespace Harmonia.WinUI.Views; namespace Harmonia.WinUI.Views;
@@ -179,6 +181,7 @@ public sealed partial class PlaylistView : UserControl
{ {
int hashCode = image.GetHashCode(); int hashCode = image.GetHashCode();
//BitmapImage? bitmapImage = await _viewModel.GetBitmapImageAsync(hashCode, playlistSong); //BitmapImage? bitmapImage = await _viewModel.GetBitmapImageAsync(hashCode, playlistSong);
//SongPictureInfo? songPictureInfo = await _viewModel.GetSongPictureInfoAsync(hashCode, playlistSong);
DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () => DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
{ {
@@ -248,4 +251,14 @@ public sealed partial class PlaylistView : UserControl
} }
} }
} }
private void PlaylistListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
_viewModel.IsUserUpdating = true;
}
private void PlaylistListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
_viewModel.IsUserUpdating = false;
}
} }