Compare commits
2 Commits
a85c65e833
...
62e53a29d4
| Author | SHA1 | Date | |
|---|---|---|---|
| 62e53a29d4 | |||
| 7cfcbf26cc |
@@ -6,6 +6,7 @@ namespace Harmonia.Core.Engine;
|
|||||||
public class BassAudioEngine : IAudioEngine, IDisposable
|
public class BassAudioEngine : IAudioEngine, IDisposable
|
||||||
{
|
{
|
||||||
private readonly BaseMediaPlayer _mediaPlayer;
|
private readonly BaseMediaPlayer _mediaPlayer;
|
||||||
|
private readonly SemaphoreSlim _loadLock = new(1, 1);
|
||||||
|
|
||||||
private CancellationTokenSource? _cancellationTokenSource;
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
|
|
||||||
@@ -136,9 +137,20 @@ public class BassAudioEngine : IAudioEngine, IDisposable
|
|||||||
private async Task<bool> LoadWaveSourceAsync(string fileName)
|
private async Task<bool> LoadWaveSourceAsync(string fileName)
|
||||||
{
|
{
|
||||||
_cancellationTokenSource?.Cancel();
|
_cancellationTokenSource?.Cancel();
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
CancellationToken token = _cancellationTokenSource.Token;
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
_cancellationTokenSource = cancellationTokenSource;
|
||||||
|
|
||||||
|
CancellationToken token = cancellationTokenSource.Token;
|
||||||
|
|
||||||
|
// Serialize loads so overlapping MediaPlayer.LoadAsync calls can't
|
||||||
|
// race and leave orphaned streams playing.
|
||||||
|
await _loadLock.WaitAsync(CancellationToken.None);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -154,10 +166,20 @@ public class BassAudioEngine : IAudioEngine, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// A newer load superseded this one; make sure this stream
|
||||||
|
// doesn't keep playing in the background.
|
||||||
|
_mediaPlayer.Stop();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loadLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateSource(string fileName)
|
private void UpdateSource(string fileName)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ public class AudioPlayer : IAudioPlayer
|
|||||||
private readonly IAudioEngine _audioEngine;
|
private readonly IAudioEngine _audioEngine;
|
||||||
private readonly IPlaylistManager _playlistManager;
|
private readonly IPlaylistManager _playlistManager;
|
||||||
|
|
||||||
|
private int _loadVersion;
|
||||||
|
|
||||||
private Playlist? _playlist;
|
private Playlist? _playlist;
|
||||||
public Playlist? Playlist
|
public Playlist? Playlist
|
||||||
{
|
{
|
||||||
@@ -241,8 +243,15 @@ public class AudioPlayer : IAudioPlayer
|
|||||||
|
|
||||||
CurrentPlaylistSong = song;
|
CurrentPlaylistSong = song;
|
||||||
|
|
||||||
|
int loadVersion = Interlocked.Increment(ref _loadVersion);
|
||||||
|
|
||||||
bool isLoaded = await TryLoadAsync(song);
|
bool isLoaded = await TryLoadAsync(song);
|
||||||
|
|
||||||
|
// A newer load request was started while this one was in flight;
|
||||||
|
// abandon this one so only the latest request controls playback.
|
||||||
|
if (loadVersion != Volatile.Read(ref _loadVersion))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (isLoaded == false)
|
if (isLoaded == false)
|
||||||
{
|
{
|
||||||
if (mode == PlaybackMode.LoadAndPlay)
|
if (mode == PlaybackMode.LoadAndPlay)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Harmonia.Core.Extensions;
|
using Harmonia.Core.Extensions;
|
||||||
using Harmonia.WinUI.Caching;
|
using Harmonia.WinUI.Caching;
|
||||||
|
using Harmonia.WinUI.Messaging;
|
||||||
using Harmonia.WinUI.Storage;
|
using Harmonia.WinUI.Storage;
|
||||||
using Harmonia.WinUI.ViewModels;
|
using Harmonia.WinUI.ViewModels;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -23,10 +24,12 @@ public partial class App : Application
|
|||||||
//services.AddSingleton<MainViewModel>();
|
//services.AddSingleton<MainViewModel>();
|
||||||
services.AddSingleton<PlayerViewModel>();
|
services.AddSingleton<PlayerViewModel>();
|
||||||
services.AddSingleton<PlayingSongViewModel>();
|
services.AddSingleton<PlayingSongViewModel>();
|
||||||
|
services.AddSingleton<PlaylistsViewModel>();
|
||||||
services.AddSingleton<PlaylistDetailViewModel>();
|
services.AddSingleton<PlaylistDetailViewModel>();
|
||||||
|
|
||||||
services.AddSingleton<IAudioBitmapImageCache, AudioBitmapImageCache>();
|
services.AddSingleton<IAudioBitmapImageCache, AudioBitmapImageCache>();
|
||||||
services.AddSingleton<IStorageProvider, WindowsStorageProvider>();
|
services.AddSingleton<IStorageProvider, WindowsStorageProvider>();
|
||||||
|
services.AddSingleton<IMessageService, WindowsMessageService>();
|
||||||
|
|
||||||
services.AddHarmonia();
|
services.AddHarmonia();
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Resources\Styles.xaml" />
|
<None Remove="Resources\Styles.xaml" />
|
||||||
<None Remove="Views\PlayerView.xaml" />
|
<None Remove="Views\PlayerView.xaml" />
|
||||||
|
<None Remove="Views\PlaylistsView.xaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -55,6 +56,11 @@
|
|||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Views\PlaylistsView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Update="Resources\Geometry.xaml">
|
<Page Update="Resources\Geometry.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
|
|||||||
@@ -46,9 +46,11 @@
|
|||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition></ColumnDefinition>
|
<ColumnDefinition></ColumnDefinition>
|
||||||
<ColumnDefinition></ColumnDefinition>
|
<ColumnDefinition></ColumnDefinition>
|
||||||
|
<ColumnDefinition></ColumnDefinition>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<views:PlayingSongView Grid.Column="0"></views:PlayingSongView>
|
<views:PlayingSongView Grid.Column="0"></views:PlayingSongView>
|
||||||
<views:PlaylistDetailView Grid.Column="1"></views:PlaylistDetailView>
|
<views:PlaylistsView Grid.Column="1"></views:PlaylistsView>
|
||||||
|
<views:PlaylistDetailView Grid.Column="2"></views:PlaylistDetailView>
|
||||||
</Grid>
|
</Grid>
|
||||||
<views:PlayerView Grid.Row="2"></views:PlayerView>
|
<views:PlayerView Grid.Row="2"></views:PlayerView>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
8
Harmonia.WinUI/Messaging/IMessageService.cs
Normal file
8
Harmonia.WinUI/Messaging/IMessageService.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Harmonia.WinUI.Messaging;
|
||||||
|
|
||||||
|
public interface IMessageService
|
||||||
|
{
|
||||||
|
Task<bool> ConfirmAsync(string title, string message);
|
||||||
|
}
|
||||||
30
Harmonia.WinUI/Messaging/WindowsMessageService.cs
Normal file
30
Harmonia.WinUI/Messaging/WindowsMessageService.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Harmonia.WinUI.Messaging;
|
||||||
|
|
||||||
|
public class WindowsMessageService : IMessageService
|
||||||
|
{
|
||||||
|
private static MainWindow MainWindow => App.ServiceProvider.GetRequiredService<MainWindow>();
|
||||||
|
|
||||||
|
public async Task<bool> ConfirmAsync(string title, string message)
|
||||||
|
{
|
||||||
|
ContentDialog dialog = new()
|
||||||
|
{
|
||||||
|
XamlRoot = MainWindow.Content.XamlRoot,
|
||||||
|
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
|
||||||
|
Title = title,
|
||||||
|
Content = message,
|
||||||
|
PrimaryButtonText = "Yes",
|
||||||
|
CloseButtonText = "No",
|
||||||
|
DefaultButton = ContentDialogButton.Primary
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await dialog.ShowAsync();
|
||||||
|
|
||||||
|
return result == ContentDialogResult.Primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,5 +141,11 @@
|
|||||||
M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3
|
M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3
|
||||||
</x:String>
|
</x:String>
|
||||||
|
|
||||||
|
<x:String x:Key="MusicNoteList">
|
||||||
|
M12 13c0 1.105-1.12 2-2.5 2S7 14.105 7 13s1.12-2 2.5-2 2.5.895 2.5 2
|
||||||
|
M12 3v10h-1V3z
|
||||||
|
M11 2.82a1 1 0 0 1 .804-.98l3-.6A1 1 0 0 1 16 2.22V4l-5 1z
|
||||||
|
M0 11.5a.5.5 0 0 1 .5-.5H4a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5m0-4A.5.5 0 0 1 .5 7H8a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5m0-4A.5.5 0 0 1 .5 3H8a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5
|
||||||
|
</x:String>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -20,6 +20,139 @@
|
|||||||
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
|
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- Premium Palette (from Harmonia icon set) -->
|
||||||
|
<LinearGradientBrush x:Key="PremiumGoldGradient" StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<GradientStop Color="#E9CFA3" Offset="0"/>
|
||||||
|
<GradientStop Color="#C7A468" Offset="0.5"/>
|
||||||
|
<GradientStop Color="#8A6D42" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
|
||||||
|
<LinearGradientBrush x:Key="PremiumTileGradient" StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<GradientStop Color="#2B2830" Offset="0"/>
|
||||||
|
<GradientStop Color="#1E1B22" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PremiumForegroundBrush" Color="#E9CFA3"/>
|
||||||
|
|
||||||
|
<!-- Accent: Teal (Shuffle / Repeat) -->
|
||||||
|
<LinearGradientBrush x:Key="PremiumTealGradient" StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<GradientStop Color="#A8DFD3" Offset="0"/>
|
||||||
|
<GradientStop Color="#6FBFAE" Offset="0.5"/>
|
||||||
|
<GradientStop Color="#3E7A6C" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
|
||||||
|
<LinearGradientBrush x:Key="PremiumTealTileGradient" StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<GradientStop Color="#26302D" Offset="0"/>
|
||||||
|
<GradientStop Color="#1A211F" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PremiumTealForegroundBrush" Color="#A8DFD3"/>
|
||||||
|
|
||||||
|
<!-- Accent: Lavender (Previous / Next) -->
|
||||||
|
<LinearGradientBrush x:Key="PremiumLavenderGradient" StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<GradientStop Color="#CBB8EF" Offset="0"/>
|
||||||
|
<GradientStop Color="#A78FD6" Offset="0.5"/>
|
||||||
|
<GradientStop Color="#6C5799" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
|
||||||
|
<LinearGradientBrush x:Key="PremiumLavenderTileGradient" StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<GradientStop Color="#2A2733" Offset="0"/>
|
||||||
|
<GradientStop Color="#1D1B24" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PremiumLavenderForegroundBrush" Color="#CBB8EF"/>
|
||||||
|
|
||||||
|
<!-- Accent: Pink (Radio / Podcasts) -->
|
||||||
|
<LinearGradientBrush x:Key="PremiumPinkGradient" StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<GradientStop Color="#EFB8C8" Offset="0"/>
|
||||||
|
<GradientStop Color="#D68FA8" Offset="0.5"/>
|
||||||
|
<GradientStop Color="#99576E" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
|
||||||
|
<LinearGradientBrush x:Key="PremiumPinkTileGradient" StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<GradientStop Color="#332730" Offset="0"/>
|
||||||
|
<GradientStop Color="#241B21" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PremiumPinkForegroundBrush" Color="#EFB8C8"/>
|
||||||
|
|
||||||
|
<!-- Premium Button -->
|
||||||
|
<Style x:Key="PremiumButton" TargetType="Button">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource PremiumForegroundBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource PremiumGoldGradient}"/>
|
||||||
|
<Setter Property="Background" Value="{StaticResource PremiumTileGradient}"/>
|
||||||
|
<Setter Property="Padding" Value="14,8"/>
|
||||||
|
<Setter Property="CornerRadius" Value="12"/>
|
||||||
|
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<!-- Outer gradient acts as the border ring -->
|
||||||
|
<Grid x:Name="Root"
|
||||||
|
Background="{TemplateBinding BorderBrush}"
|
||||||
|
CornerRadius="{TemplateBinding CornerRadius}"
|
||||||
|
Padding="1.5">
|
||||||
|
<Grid x:Name="Inner"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
CornerRadius="10">
|
||||||
|
<ContentPresenter x:Name="ContentPresenter"
|
||||||
|
Content="{TemplateBinding Content}"
|
||||||
|
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
Foreground="{TemplateBinding Foreground}"
|
||||||
|
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
|
||||||
|
</Grid>
|
||||||
|
<VisualStateManager.VisualStateGroups>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal"/>
|
||||||
|
<VisualState x:Name="PointerOver">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Target="Inner.Opacity" Value="0.88"/>
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="Pressed">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Target="Inner.Opacity" Value="0.75"/>
|
||||||
|
<Setter Target="Root.Opacity" Value="0.9"/>
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Target="Root.Opacity" Value="0.4"/>
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateManager.VisualStateGroups>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Premium Button (Teal) -->
|
||||||
|
<Style x:Key="PremiumButton-Teal" TargetType="Button" BasedOn="{StaticResource PremiumButton}">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource PremiumTealForegroundBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource PremiumTealGradient}"/>
|
||||||
|
<Setter Property="Background" Value="{StaticResource PremiumTealTileGradient}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Premium Button (Lavender) -->
|
||||||
|
<Style x:Key="PremiumButton-Lavender" TargetType="Button" BasedOn="{StaticResource PremiumButton}">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource PremiumLavenderForegroundBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource PremiumLavenderGradient}"/>
|
||||||
|
<Setter Property="Background" Value="{StaticResource PremiumLavenderTileGradient}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Premium Button (Pink) -->
|
||||||
|
<Style x:Key="PremiumButton-Pink" TargetType="Button" BasedOn="{StaticResource PremiumButton}">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource PremiumPinkForegroundBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource PremiumPinkGradient}"/>
|
||||||
|
<Setter Property="Background" Value="{StaticResource PremiumPinkTileGradient}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- Flat Button -->
|
<!-- Flat Button -->
|
||||||
<Style x:Key="FlatButton" TargetType="Button">
|
<Style x:Key="FlatButton" TargetType="Button">
|
||||||
<Setter Property="Padding" Value="10"/>
|
<Setter Property="Padding" Value="10"/>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Harmonia.Core.Player;
|
|||||||
using Harmonia.Core.Playlists;
|
using Harmonia.Core.Playlists;
|
||||||
using Harmonia.Core.Scanner;
|
using Harmonia.Core.Scanner;
|
||||||
using Harmonia.WinUI.Caching;
|
using Harmonia.WinUI.Caching;
|
||||||
|
using Harmonia.WinUI.Messaging;
|
||||||
using Harmonia.WinUI.Storage;
|
using Harmonia.WinUI.Storage;
|
||||||
using Microsoft.UI.Xaml.Media.Imaging;
|
using Microsoft.UI.Xaml.Media.Imaging;
|
||||||
using System;
|
using System;
|
||||||
@@ -36,6 +37,7 @@ public partial class PlaylistDetailViewModel : ViewModelBase
|
|||||||
private readonly IAudioFileScanner _audioFileScanner;
|
private readonly IAudioFileScanner _audioFileScanner;
|
||||||
private readonly IAudioEngine _audioEngine;
|
private readonly IAudioEngine _audioEngine;
|
||||||
private readonly IStorageProvider _storageProvider;
|
private readonly IStorageProvider _storageProvider;
|
||||||
|
private readonly IMessageService _messageService;
|
||||||
private readonly DispatcherQueue _dispatcherQueue;
|
private readonly DispatcherQueue _dispatcherQueue;
|
||||||
private readonly ConcurrentDictionary<int, CancellationTokenSource> _imageCancellationTokens = [];
|
private readonly ConcurrentDictionary<int, CancellationTokenSource> _imageCancellationTokens = [];
|
||||||
|
|
||||||
@@ -100,6 +102,7 @@ public partial class PlaylistDetailViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ICommand PlaySongCommand => new AsyncRelayCommand(PlaySongAsync, AreSongsSelected);
|
public ICommand PlaySongCommand => new AsyncRelayCommand(PlaySongAsync, AreSongsSelected);
|
||||||
|
public ICommand NewPlaylistCommand => new AsyncRelayCommand(NewPlaylistAsync);
|
||||||
public ICommand AddFilesCommand => new AsyncRelayCommand(AddFilesAsync);
|
public ICommand AddFilesCommand => new AsyncRelayCommand(AddFilesAsync);
|
||||||
public ICommand AddFolderCommand => new AsyncRelayCommand(AddFolderAsync);
|
public ICommand AddFolderCommand => new AsyncRelayCommand(AddFolderAsync);
|
||||||
public ICommand RemoveSongsCommand => new RelayCommand(RemoveSongs, AreSongsSelected);
|
public ICommand RemoveSongsCommand => new RelayCommand(RemoveSongs, AreSongsSelected);
|
||||||
@@ -110,6 +113,7 @@ public partial class PlaylistDetailViewModel : ViewModelBase
|
|||||||
public ICommand RefreshTagsCommand => new RelayCommand(RefreshTags);
|
public ICommand RefreshTagsCommand => new RelayCommand(RefreshTags);
|
||||||
public ICommand RemoveMissingSongsCommand => new RelayCommand(RemoveMissingSongs);
|
public ICommand RemoveMissingSongsCommand => new RelayCommand(RemoveMissingSongs);
|
||||||
public ICommand RemoveDuplicateSongsCommand => new RelayCommand(RemoveDuplicateSongs);
|
public ICommand RemoveDuplicateSongsCommand => new RelayCommand(RemoveDuplicateSongs);
|
||||||
|
public ICommand DeletePlaylistCommand => new AsyncRelayCommand(DeletePlaylistAsync);
|
||||||
|
|
||||||
public bool IsUserUpdating { get; set; }
|
public bool IsUserUpdating { get; set; }
|
||||||
private bool _isUserInitiatingSongChange;
|
private bool _isUserInitiatingSongChange;
|
||||||
@@ -125,10 +129,11 @@ public partial class PlaylistDetailViewModel : ViewModelBase
|
|||||||
IAudioFileScanner audioFileScanner,
|
IAudioFileScanner audioFileScanner,
|
||||||
IAudioEngine audioEngine,
|
IAudioEngine audioEngine,
|
||||||
IStorageProvider storageProvider,
|
IStorageProvider storageProvider,
|
||||||
IPlaylistManager playlistManager)
|
IPlaylistManager playlistManager,
|
||||||
|
IMessageService messageService)
|
||||||
{
|
{
|
||||||
_playlistManager = playlistManager;
|
_playlistManager = playlistManager;
|
||||||
//_playlistManager.CurrentPlaylistChanged += OnPlaylistChanged;
|
_playlistManager.CurrentPlaylistChanged += OnPlaylistChanged;
|
||||||
|
|
||||||
_audioPlayer = audioPlayer;
|
_audioPlayer = audioPlayer;
|
||||||
_audioPlayer.PlaylistChanged += OnPlaylistChanged;
|
_audioPlayer.PlaylistChanged += OnPlaylistChanged;
|
||||||
@@ -139,6 +144,7 @@ public partial class PlaylistDetailViewModel : ViewModelBase
|
|||||||
_audioFileScanner = audioFileScanner;
|
_audioFileScanner = audioFileScanner;
|
||||||
_audioEngine = audioEngine;
|
_audioEngine = audioEngine;
|
||||||
_storageProvider = storageProvider;
|
_storageProvider = storageProvider;
|
||||||
|
_messageService = messageService;
|
||||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||||
|
|
||||||
FilteredPlaylistSongs.CollectionChanged += OnFilteredPlaylistSongsCollectionChanged;
|
FilteredPlaylistSongs.CollectionChanged += OnFilteredPlaylistSongsCollectionChanged;
|
||||||
@@ -185,7 +191,8 @@ public partial class PlaylistDetailViewModel : ViewModelBase
|
|||||||
private void OnPlaylistChanged(object? sender, EventArgs e)
|
private void OnPlaylistChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Playlist?.PlaylistUpdated -= OnPlaylistUpdated;
|
Playlist?.PlaylistUpdated -= OnPlaylistUpdated;
|
||||||
Playlist = _audioPlayer.Playlist;
|
//Playlist = _audioPlayer.Playlist;
|
||||||
|
Playlist = _playlistManager.CurrentPlaylist;
|
||||||
Playlist?.PlaylistUpdated += OnPlaylistUpdated;
|
Playlist?.PlaylistUpdated += OnPlaylistUpdated;
|
||||||
|
|
||||||
UpdateFilteredSongs();
|
UpdateFilteredSongs();
|
||||||
@@ -369,6 +376,12 @@ public partial class PlaylistDetailViewModel : ViewModelBase
|
|||||||
return SelectedPlaylistSongs.Count > 0;
|
return SelectedPlaylistSongs.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task NewPlaylistAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Playlist playlist = await _playlistManager.AddPlaylistAsync();
|
||||||
|
_playlistManager.CurrentPlaylist = playlist;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task AddFilesAsync(CancellationToken cancellationToken)
|
private async Task AddFilesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (Playlist == null)
|
if (Playlist == null)
|
||||||
@@ -535,5 +548,30 @@ public partial class PlaylistDetailViewModel : ViewModelBase
|
|||||||
Playlist?.RemoveDuplicateSongs();
|
Playlist?.RemoveDuplicateSongs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task DeletePlaylistAsync()
|
||||||
|
{
|
||||||
|
if (Playlist == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool confirmed = await _messageService.ConfirmAsync($"Delete Playlist - {Playlist.Name}", "Are you sure you want to delete this playlist?");
|
||||||
|
|
||||||
|
if (!confirmed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_playlistManager.RemovePlaylist(Playlist);
|
||||||
|
|
||||||
|
if (_playlistManager.CurrentPlaylist == Playlist)
|
||||||
|
{
|
||||||
|
if (_playlistManager.Playlists.Count > 0)
|
||||||
|
{
|
||||||
|
_playlistManager.CurrentPlaylist = _playlistManager.Playlists.ElementAt(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_playlistManager.CurrentPlaylist = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
65
Harmonia.WinUI/ViewModels/PlaylistsViewModel.cs
Normal file
65
Harmonia.WinUI/ViewModels/PlaylistsViewModel.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using Harmonia.Core.Playlists;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace Harmonia.WinUI.ViewModels;
|
||||||
|
|
||||||
|
public partial class PlaylistsViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly IPlaylistManager _playlistManager;
|
||||||
|
|
||||||
|
private ObservableCollection<Playlist> _playlists = [];
|
||||||
|
public ObservableCollection<Playlist> Playlists
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _playlists;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _playlists, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Playlist? _selectedPlaylist = null;
|
||||||
|
public Playlist? SelectedPlaylist
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _selectedPlaylist;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedPlaylist, value))
|
||||||
|
{
|
||||||
|
_playlistManager.CurrentPlaylist = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlaylistsViewModel(IPlaylistManager playlistManager)
|
||||||
|
{
|
||||||
|
_playlistManager = playlistManager;
|
||||||
|
_playlistManager.PlaylistAdded += OnPlaylistAdded;
|
||||||
|
_playlistManager.PlaylistRemoved += OnPlaylistRemoved;
|
||||||
|
_playlistManager.CurrentPlaylistChanged += OnCurrentPlaylistChanged;
|
||||||
|
|
||||||
|
_playlists = new(_playlistManager.Playlists);
|
||||||
|
_selectedPlaylist = _playlistManager.CurrentPlaylist;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCurrentPlaylistChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
SelectedPlaylist = _playlistManager.CurrentPlaylist;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlaylistAdded(object? sender, PlaylistAddedEventArgs e)
|
||||||
|
{
|
||||||
|
Playlists.Add(e.Playlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlaylistRemoved(object? sender, PlaylistRemovedEventArgs e)
|
||||||
|
{
|
||||||
|
Playlists.Remove(e.Playlist);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,9 @@ public class ViewModelLocator
|
|||||||
public static PlayingSongViewModel PlayingSongViewModel
|
public static PlayingSongViewModel PlayingSongViewModel
|
||||||
=> App.ServiceProvider.GetRequiredService<PlayingSongViewModel>();
|
=> App.ServiceProvider.GetRequiredService<PlayingSongViewModel>();
|
||||||
|
|
||||||
|
public static PlaylistsViewModel PlaylistsViewModel
|
||||||
|
=> App.ServiceProvider.GetRequiredService<PlaylistsViewModel>();
|
||||||
|
|
||||||
public static PlaylistDetailViewModel PlaylistDetailViewModel
|
public static PlaylistDetailViewModel PlaylistDetailViewModel
|
||||||
=> App.ServiceProvider.GetRequiredService<PlaylistDetailViewModel>();
|
=> App.ServiceProvider.GetRequiredService<PlaylistDetailViewModel>();
|
||||||
}
|
}
|
||||||
@@ -123,6 +123,11 @@
|
|||||||
<Setter Property="Background" Value="Transparent"></Setter>
|
<Setter Property="Background" Value="Transparent"></Setter>
|
||||||
</Style>
|
</Style>
|
||||||
</MenuFlyout.MenuFlyoutPresenterStyle>
|
</MenuFlyout.MenuFlyoutPresenterStyle>
|
||||||
|
<MenuFlyoutItem Text="New Playlist" Command="{Binding NewPlaylistCommand}">
|
||||||
|
<MenuFlyoutItem.Icon>
|
||||||
|
<PathIcon Data="{StaticResource AddFileIcon}"></PathIcon>
|
||||||
|
</MenuFlyoutItem.Icon>
|
||||||
|
</MenuFlyoutItem>
|
||||||
<MenuFlyoutItem Text="Add Files..." Command="{Binding AddFilesCommand}">
|
<MenuFlyoutItem Text="Add Files..." Command="{Binding AddFilesCommand}">
|
||||||
<MenuFlyoutItem.Icon>
|
<MenuFlyoutItem.Icon>
|
||||||
<PathIcon Data="{StaticResource AddFileIcon}"></PathIcon>
|
<PathIcon Data="{StaticResource AddFileIcon}"></PathIcon>
|
||||||
@@ -167,7 +172,7 @@
|
|||||||
|
|
||||||
</MenuFlyoutItem>
|
</MenuFlyoutItem>
|
||||||
<MenuFlyoutSeparator></MenuFlyoutSeparator>
|
<MenuFlyoutSeparator></MenuFlyoutSeparator>
|
||||||
<MenuFlyoutItem Text="Remove Playlist" Foreground="#ff99a4">
|
<MenuFlyoutItem Text="Remove Playlist" Foreground="#ff99a4" Command="{Binding DeletePlaylistCommand}">
|
||||||
|
|
||||||
</MenuFlyoutItem>
|
</MenuFlyoutItem>
|
||||||
<MenuFlyoutSeparator></MenuFlyoutSeparator>
|
<MenuFlyoutSeparator></MenuFlyoutSeparator>
|
||||||
@@ -177,6 +182,12 @@
|
|||||||
</MenuFlyout>
|
</MenuFlyout>
|
||||||
</Button.Flyout>
|
</Button.Flyout>
|
||||||
</Button>
|
</Button>
|
||||||
|
<!--<Button Style="{StaticResource PremiumButton-Teal}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<PathIcon Style="{StaticResource FlatButtonIcon}" Foreground="{StaticResource PremiumTeaForegroundBrush}" Data="{StaticResource AddIcon}" />
|
||||||
|
<TextBlock Text="Go Premium"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>-->
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ListView
|
<ListView
|
||||||
|
|||||||
31
Harmonia.WinUI/Views/PlaylistsView.xaml
Normal file
31
Harmonia.WinUI/Views/PlaylistsView.xaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<UserControl
|
||||||
|
x:Class="Harmonia.WinUI.Views.PlaylistsView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Harmonia.WinUI.Views"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:Harmonia.WinUI.ViewModels"
|
||||||
|
xmlns:playlists="using:Harmonia.Core.Playlists"
|
||||||
|
DataContext="{Binding Source={StaticResource Locator}, Path=PlaylistsViewModel}"
|
||||||
|
d:DataContext="{d:DesignInstance Type=vm:PlaylistsViewModel, IsDesignTimeCreatable=True}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<DataTemplate x:Key="PlaylistTemplate" x:DataType="playlists:Playlist">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Bottom">
|
||||||
|
<Viewbox Height="24" Width="24">
|
||||||
|
<PathIcon Data="{StaticResource MusicNoteList}" />
|
||||||
|
</Viewbox>
|
||||||
|
<TextBlock Text="{x:Bind Name}" FontSize="16" VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid Margin="10">
|
||||||
|
<ListView
|
||||||
|
ItemsSource="{Binding Playlists, Mode=OneWay}"
|
||||||
|
SelectedItem="{Binding SelectedPlaylist, Mode=TwoWay}"
|
||||||
|
ItemTemplate="{StaticResource PlaylistTemplate}">
|
||||||
|
</ListView>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
12
Harmonia.WinUI/Views/PlaylistsView.xaml.cs
Normal file
12
Harmonia.WinUI/Views/PlaylistsView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
|
namespace Harmonia.WinUI.Views;
|
||||||
|
|
||||||
|
public sealed partial class PlaylistsView : UserControl
|
||||||
|
{
|
||||||
|
public PlaylistsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user