Compare commits

..

11 Commits

32 changed files with 339 additions and 47 deletions

View File

@@ -9,10 +9,10 @@
<ItemGroup>
<PackageReference Include="ManagedBass" Version="4.0.2" />
<PackageReference Include="ManagedBass.Flac" Version="4.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.12" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
</ItemGroup>

View File

@@ -16,14 +16,14 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.0" />
<PackageReference Include="NSubstitute" Version="6.2.0" />
<PackageReference Include="Shouldly" Version="4.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.v3" Version="4.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -8,6 +8,32 @@ namespace Harmonia.Tests;
public class PlaylistTests
{
[Fact]
public void Create_Playlist()
{
Playlist playlist = new();
playlist.Name.ShouldBeNull();
playlist.Songs.ShouldBeEmpty();
}
[Fact]
public void Create_Playlist_With_Name()
{
Playlist playlist = new("My Playlist");
playlist.Name.ShouldBe("My Playlist");
playlist.Songs.ShouldBeEmpty();
}
[Fact]
public void Set_Playlist_Name()
{
Playlist playlist = new();
playlist.Name.ShouldBeNull();
playlist.SetName("My Playlist");
playlist.Name.ShouldBe("My Playlist");
}
[Fact]
public void Add_Songs()
{
@@ -37,6 +63,70 @@ public class PlaylistTests
playlist.Songs[1].Song.FileName.ShouldBe("Song5.mp3");
}
[Fact]
public void Move_Songs()
{
Playlist playlist = new();
Song[] songs =
[
new Song() { FileName = "Song1.mp3" },
new Song() { FileName = "Song2.mp3" },
new Song() { FileName = "Song3.mp3" },
new Song() { FileName = "Song4.mp3" },
new Song() { FileName = "Song5.mp3" },
];
playlist.AddSongs(songs);
playlist.MoveSong(2, 3);
string[] expectedFileNames =
[
"Song1.mp3",
"Song2.mp3",
"Song4.mp3",
"Song3.mp3",
"Song5.mp3"
];
playlist.Songs.Select(x => x.Song.FileName).ToArray()
.ShouldBeEquivalentTo(expectedFileNames);
}
[Fact]
public void Remove_Duplicate_Songs()
{
Playlist playlist = new();
Song[] songs =
[
new Song() { FileName = "Song1.mp3" },
new Song() { FileName = "Song2.mp3" },
new Song() { FileName = "Song3.mp3" },
new Song() { FileName = "Song4.mp3" },
new Song() { FileName = "Song5.mp3" },
new Song() { FileName = "Song2.mp3" },
new Song() { FileName = "Song3.mp3" },
];
playlist.AddSongs(songs);
playlist.RemoveDuplicateSongs();
string[] expectedFileNames =
[
"Song1.mp3",
"Song2.mp3",
"Song3.mp3",
"Song4.mp3",
"Song5.mp3"
];
playlist.Songs.Select(x => x.Song.FileName).ToArray()
.ShouldBeEquivalentTo(expectedFileNames);
}
[Fact]
public void Sort_Songs()
{
@@ -74,6 +164,34 @@ public class PlaylistTests
.ShouldBeEquivalentTo(expectedSortedFileNames);
}
[Fact]
public void Reverse_Songs()
{
Playlist playlist = new();
Song[] songs =
[
new Song() { FileName = "Song1.mp3" },
new Song() { FileName = "Song2.mp3" },
new Song() { FileName = "Song3.mp3" }
];
playlist.AddSongs(songs);
PlaylistSong[] playlistSongs = [.. playlist.Songs];
playlist.Reverse(playlistSongs);
string[] expectedReversedFileNames =
[
"Song3.mp3",
"Song2.mp3",
"Song1.mp3"
];
playlist.Songs.Select(x => x.Song.FileName).ToArray()
.ShouldBeEquivalentTo(expectedReversedFileNames);
}
[Fact]
public void Remove_Songs()
{
@@ -101,7 +219,7 @@ public class PlaylistTests
}
[Fact]
public void Lock_Playlist()
public void Lock_And_Unlock_Playlist()
{
Playlist playlist = new();
@@ -114,8 +232,12 @@ public class PlaylistTests
playlist.AddSongs(songs);
playlist.IsLocked.ShouldBeFalse();
playlist.Lock();
playlist.IsLocked.ShouldBeTrue();
Song song = new() { FileName = "Song4.mp3" };
playlist.AddSong(song);
@@ -124,11 +246,21 @@ public class PlaylistTests
playlist.RemoveSong(0);
playlist.Songs.Count.ShouldBe(3);
}
//public void Get_Playlists()
//{
// //PlaylistRepository playlistRepository = new();
// //playlistRepository.Get().Returns()
//}
playlist.MoveSong(0, 2);
string[] expectedMovedFileNamesOnLockedPlaylist =
[
"Song1.mp3",
"Song2.mp3",
"Song3.mp3"
];
playlist.Songs.Select(x => x.Song.FileName).ToArray()
.ShouldBeEquivalentTo(expectedMovedFileNamesOnLockedPlaylist);
playlist.Unlock();
playlist.IsLocked.ShouldBeFalse();
}
}

View File

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

View File

@@ -11,15 +11,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.1" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.1.1" />
<PackageReference Include="Avalonia" Version="12.1.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.2" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.1.2" />
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Egolds.Xaml.Behaviors.Interactions.Animated" Version="11.3.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.11" />
<PackageReference Include="Egolds.Xaml.Behaviors.Interactions.Animated" Version="11.4.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.12" />
<PackageReference Include="Semi.Avalonia" Version="12.1.0.1" />
</ItemGroup>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
@@ -14,6 +14,7 @@
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\Styles.xaml" />
<None Remove="Views\PlayerView.xaml" />
@@ -30,6 +31,10 @@
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\Fonts\*.ttf" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
@@ -45,8 +50,8 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.251219" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.2526" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.3.1" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.2705" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Harmonia.Core\Harmonia.Core.csproj" />
@@ -102,5 +107,6 @@
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>

View File

@@ -40,7 +40,7 @@
<!--<Image Grid.Column="0" x:Name="TitleBarIcon" Source="ms-appx:///Assets/Images/MangaReader.png" Width="20" Height="20" Margin="0 0 10 0" />-->
<TextBlock Grid.Column="1" x:Name="AppTitle" Text="{x:Bind Title, Mode=OneWay}" Style="{StaticResource CaptionTextBlockStyle}" VerticalAlignment="Center" />
</Grid>
<Grid Grid.Row="1" Grid.RowSpan="2">
<!--<Grid Grid.Row="1" Grid.RowSpan="2">
<Image Source="/Assets/Default.png" Stretch="UniformToFill" VerticalAlignment="Center" HorizontalAlignment="Center"></Image>
<Canvas Background="#99000000"></Canvas>
<Border>
@@ -48,7 +48,7 @@
<Media:BackdropBlurBrush Amount="20"></Media:BackdropBlurBrush>
</Border.Background>
</Border>
</Grid>
</Grid>-->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>

View File

@@ -1,6 +1,9 @@
using Harmonia.Core.Player;
using Harmonia.WinUI.Session;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
using System.Runtime.InteropServices;
namespace Harmonia.WinUI;
@@ -8,16 +11,23 @@ public sealed partial class MainWindow : Window
{
private const string ApplicationTitle = "Harmonia";
[LibraryImport("kernel32.dll", SetLastError = true)]
private static partial EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
private readonly ISessionService _sessionService;
private readonly ISessionTracker _sessionTracker;
private readonly IAudioPlayer _audioPlayer;
private bool _isSaved;
public MainWindow(ISessionService sessionService, ISessionTracker sessionTracker)
public MainWindow(ISessionService sessionService, ISessionTracker sessionTracker, IAudioPlayer audioPlayer)
{
_sessionService = sessionService;
_sessionTracker = sessionTracker;
_audioPlayer = audioPlayer;
_audioPlayer.PropertyChanged += OnAudioPlayerPropertyChanged;
InitializeComponent();
InitializeTitleBar();
InitializeWindowState();
@@ -54,6 +64,8 @@ public sealed partial class MainWindow : Window
private async void OnMainWindowClosed(object sender, WindowEventArgs args)
{
EnableSleepIdleTimeout();
if (_isSaved)
return;
@@ -65,4 +77,53 @@ public sealed partial class MainWindow : Window
_isSaved = true;
Close();
}
private void OnAudioPlayerPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(_audioPlayer.State):
OnAudioPlayerStateChanged(sender, e);
break;
}
}
private void OnAudioPlayerStateChanged(object? sender, EventArgs e)
{
// SetThreadExecutionState is per-thread, so always marshal to the UI thread to
// guarantee the ES_CONTINUOUS flags are set and cleared on the same thread.
DispatcherQueue.TryEnqueue(() =>
{
switch (_audioPlayer.State)
{
case Core.Engine.AudioPlaybackState.Playing:
DisableSleepIdleTimeout();
break;
default:
EnableSleepIdleTimeout();
break;
}
});
}
private static void EnableSleepIdleTimeout()
{
// Clear EXECUTION_STATE flags to allow the system to idle to sleep normally.
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
}
private static void DisableSleepIdleTimeout()
{
// Prevent the system sleep idle time-out while still allowing the display to sleep.
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED);
}
[Flags]
private enum EXECUTION_STATE : uint
{
ES_CONTINUOUS = 0x80000000,
ES_SYSTEM_REQUIRED = 0x00000001,
ES_DISPLAY_REQUIRED = 0x00000002,
ES_AWAYMODE_REQUIRED = 0x00000040
}
}

View File

@@ -4,29 +4,34 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Global Font -->
<FontFamily x:Key="AppFontFamily">ms-appx:///Assets/Fonts/Lexend-Regular.ttf#Lexend, ms-appx:///Assets/Fonts/NotoSansJP-Regular.ttf#Noto Sans JP</FontFamily>
<FontFamily x:Key="AppFontFamilyMedium">ms-appx:///Assets/Fonts/Lexend-Medium.ttf#Lexend, ms-appx:///Assets/Fonts/NotoSansJP-Medium.ttf#Noto Sans JP</FontFamily>
<FontFamily x:Key="AppFontFamilySemiBold">ms-appx:///Assets/Fonts/Lexend-SemiBold.ttf#Lexend, ms-appx:///Assets/Fonts/NotoSansJP-SemiBold.ttf#Noto Sans JP</FontFamily>
<FontFamily x:Key="AppFontFamilyBold">ms-appx:///Assets/Fonts/Lexend-Bold.ttf#Lexend, ms-appx:///Assets/Fonts/NotoSansJP-Bold.ttf#Noto Sans JP</FontFamily>
<Style TargetType="TextBlock">
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
<Setter Property="FontFamily" Value="{StaticResource AppFontFamily}" />
</Style>
<Style TargetType="TextBox" BasedOn="{StaticResource DefaultTextBoxStyle}">
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
<Setter Property="FontFamily" Value="{StaticResource AppFontFamily}" />
</Style>
<Style TargetType="MenuFlyoutItem" BasedOn="{StaticResource DefaultMenuFlyoutItemStyle}">
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
<Setter Property="FontFamily" Value="{StaticResource AppFontFamily}" />
</Style>
<Style TargetType="MenuFlyoutSubItem" BasedOn="{StaticResource DefaultMenuFlyoutSubItemStyle}">
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
<Setter Property="FontFamily" Value="{StaticResource AppFontFamily}" />
</Style>
<Style TargetType="ToggleMenuFlyoutItem" BasedOn="{StaticResource DefaultToggleMenuFlyoutItemStyle}">
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
<Setter Property="FontFamily" Value="{StaticResource AppFontFamily}" />
</Style>
<Style TargetType="ListViewItem" BasedOn="{StaticResource DefaultListViewItemStyle}">
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
<Setter Property="FontFamily" Value="{StaticResource AppFontFamily}" />
</Style>
<!-- Premium Palette (from Harmonia icon set) -->
@@ -92,7 +97,7 @@
<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="FontFamily" Value="{StaticResource AppFontFamily}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">

View File

@@ -595,7 +595,7 @@ public partial class PlaylistDetailViewModel : ViewModelBase
private bool CanPasteSongs()
{
if (Playlist == null || SelectedPlaylistSongs.Count == 0)
if (Playlist == null)
return false;
DataPackageView dataPackageView = Clipboard.GetContent();
@@ -611,17 +611,21 @@ public partial class PlaylistDetailViewModel : ViewModelBase
private async Task PasteSongsAsync()
{
if (Playlist == null || SelectedPlaylistSongs.Count == 0)
return;
int selectedPlaylistSongIndex = Playlist.IndexOf(SelectedPlaylistSongs[0]);
if (selectedPlaylistSongIndex == -1)
if (Playlist == null)
return;
Song[] songs = await GetSongsFromClipboardAsync();
int insertIndex = Playlist.Songs.Count;
Playlist.AddSongs(songs, selectedPlaylistSongIndex + 1);
if (SelectedPlaylistSongs.Count > 0)
{
int selectedPlaylistSongIndex = Playlist.IndexOf(SelectedPlaylistSongs[0]);
if (selectedPlaylistSongIndex >= 0)
insertIndex = selectedPlaylistSongIndex + 1;
}
Playlist.AddSongs(songs, insertIndex);
}
private static async Task<Song[]> GetSongsFromClipboardAsync()

View File

@@ -52,6 +52,7 @@
</Style>
<Style x:Key="SongTitleTextBlock" TargetType="TextBlock">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontFamily" Value="{StaticResource AppFontFamilySemiBold}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Setter Property="Foreground" Value="{StaticResource SongItemTitleBrush}"/>
@@ -137,7 +138,7 @@
<Canvas Background="#19000000"></Canvas>
</Grid>
<StackPanel VerticalAlignment="Center">
<TextBlock Foreground="{StaticResource SongItemTitleBrush2}" FontWeight="SemiBold" FontSize="16" Text="{Binding Song, Converter={StaticResource SongTitle}}"></TextBlock>
<TextBlock Foreground="{StaticResource SongItemTitleBrush2}" FontFamily="{StaticResource AppFontFamilySemiBold}" FontWeight="SemiBold" FontSize="16" Text="{Binding Song, Converter={StaticResource SongTitle}}"></TextBlock>
<TextBlock Foreground="{StaticResource SongItemSubtitleBrush2}" FontSize="14" Text="{Binding Song.Artists, Converter={StaticResource ArtistsToString}}"></TextBlock>
<TextBlock Foreground="{StaticResource SongItemSubtitleBrush2}" FontSize="14" Text="{Binding Song.Album}"></TextBlock>
<TextBlock Foreground="#777" FontSize="12" Visibility="{Binding Song, Converter={StaticResource NullVisibility}}">
@@ -150,14 +151,59 @@
<!-- Playback Actions -->
<Grid Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="16">
<!--<Button Style="{StaticResource FlatButton}" Command="{Binding PreviousSongCommand}">
<Path Style="{StaticResource FlatButtonPath}" Fill="{StaticResource LightGradientBrush}" Data="{StaticResource ShuffleIcon}"></Path>
</Button>-->
<Button Style="{StaticResource FlatButton}" Command="{Binding PreviousSongCommand}">
<Path Style="{StaticResource FlatButtonPath}" Fill="{StaticResource LightGradientBrush}" Data="{StaticResource SkipStartFillIcon}"></Path>
</Button>
<Button Style="{StaticResource FlatButton}" Command="{Binding StopSongCommand}">
<Path Style="{StaticResource FlatButtonPath}" Fill="{StaticResource LightGradientBrush}" Data="{StaticResource StopFillIcon}"></Path>
</Button>
<Button Style="{StaticResource FlatButton}" Command="{Binding PlaySongCommand}" BorderBrush="{StaticResource DarkGradientBrush}" BorderThickness="2" CornerRadius="48" Padding="16">
<Path Style="{StaticResource FlatButtonPath}" Fill="{StaticResource DarkGradientBrush}" Data="{StaticResource PlayFillIcon}" Margin="1,0,-1,0"></Path>
<Button Style="{StaticResource FlatButton}" Command="{Binding PlaySongCommand}" Padding="0"
PointerEntered="PlayButton_PointerEntered" PointerExited="PlayButton_PointerExited">
<Button.Resources>
<!-- Keep background transparent on hover/press -->
<SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="Transparent"/>
<SolidColorBrush x:Key="ButtonBackgroundPressed" Color="Transparent"/>
<Storyboard x:Name="PlayHoverEnterStoryboard">
<ColorAnimation Storyboard.TargetName="PlayGradientStopTop" Storyboard.TargetProperty="Color"
To="#FDF6E2" Duration="0:0:0.15" EnableDependentAnimation="True"/>
<ColorAnimation Storyboard.TargetName="PlayGradientStopBottom" Storyboard.TargetProperty="Color"
To="#C98B4E" Duration="0:0:0.15" EnableDependentAnimation="True"/>
<ColorAnimation Storyboard.TargetName="PlayIconGradientStopTop" Storyboard.TargetProperty="Color"
To="#FDF6E2" Duration="0:0:0.15" EnableDependentAnimation="True"/>
<ColorAnimation Storyboard.TargetName="PlayIconGradientStopBottom" Storyboard.TargetProperty="Color"
To="#C98B4E" Duration="0:0:0.15" EnableDependentAnimation="True"/>
</Storyboard>
<Storyboard x:Name="PlayHoverExitStoryboard">
<ColorAnimation Storyboard.TargetName="PlayGradientStopTop" Storyboard.TargetProperty="Color"
To="#F9EBC6" Duration="0:0:0.15" EnableDependentAnimation="True"/>
<ColorAnimation Storyboard.TargetName="PlayGradientStopBottom" Storyboard.TargetProperty="Color"
To="#A66831" Duration="0:0:0.15" EnableDependentAnimation="True"/>
<ColorAnimation Storyboard.TargetName="PlayIconGradientStopTop" Storyboard.TargetProperty="Color"
To="#F9EBC6" Duration="0:0:0.15" EnableDependentAnimation="True"/>
<ColorAnimation Storyboard.TargetName="PlayIconGradientStopBottom" Storyboard.TargetProperty="Color"
To="#A66831" Duration="0:0:0.15" EnableDependentAnimation="True"/>
</Storyboard>
</Button.Resources>
<Border BorderThickness="2" CornerRadius="48" Padding="16">
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop x:Name="PlayGradientStopTop" Color="#F9EBC6" Offset="0"/>
<GradientStop x:Name="PlayGradientStopBottom" Color="#A66831" Offset="1"/>
</LinearGradientBrush>
</Border.BorderBrush>
<Path Style="{StaticResource FlatButtonPath}" Data="{StaticResource PlayFillIcon}" Margin="1,0,-1,0">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop x:Name="PlayIconGradientStopTop" Color="#F9EBC6" Offset="0"/>
<GradientStop x:Name="PlayIconGradientStopBottom" Color="#A66831" Offset="1"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
</Border>
</Button>
<Button Style="{StaticResource FlatButton}" Command="{Binding PauseSongCommand}">
<Path Style="{StaticResource FlatButtonPath}" Fill="{StaticResource LightGradientBrush}" Data="{StaticResource PauseFillIcon}"></Path>
@@ -165,6 +211,9 @@
<Button Style="{StaticResource FlatButton}" Command="{Binding NextSongCommand}">
<Path Style="{StaticResource FlatButtonPath}" Fill="{StaticResource LightGradientBrush}" Data="{StaticResource SkipEndFillIcon}"></Path>
</Button>
<!--<Button Style="{StaticResource FlatButton}" Command="{Binding NextSongCommand}">
<Path Style="{StaticResource FlatButtonPath}" Fill="{StaticResource LightGradientBrush}" Data="{StaticResource RepeatIcon}"></Path>
</Button>-->
</StackPanel>
</Grid>
@@ -178,7 +227,14 @@
</Canvas>
</Viewbox>
</Button>
<Slider Width="200" Minimum="0.0" Maximum="1.0" StepFrequency="0.01" VerticalAlignment="Center" Value="{Binding Volume, Mode=TwoWay}">
<Slider
Width="200"
Minimum="0.0"
Maximum="1.0"
StepFrequency="0.01"
VerticalAlignment="Center"
PointerWheelChanged="VolumeSlider_PointerWheelChanged"
Value="{Binding Volume, Mode=TwoWay}">
<Slider.Resources>
<!-- Thumb (the "ball") -->
<StaticResource x:Key="SliderThumbBackground" ResourceKey="DarkGradientBrush"/>

View File

@@ -50,6 +50,18 @@ public sealed partial class PlayerView : UserControl
_viewModel.IsPositionChangeInProgress = false;
}
private void PlayButton_PointerEntered(object? sender, PointerRoutedEventArgs e)
{
PlayHoverExitStoryboard.Stop();
PlayHoverEnterStoryboard.Begin();
}
private void PlayButton_PointerExited(object? sender, PointerRoutedEventArgs e)
{
PlayHoverEnterStoryboard.Stop();
PlayHoverExitStoryboard.Begin();
}
private void VolumeSlider_PointerWheelChanged(object? sender, PointerRoutedEventArgs e)
{
if (sender is not Slider slider)

View File

@@ -43,6 +43,7 @@
<!-- Song Item Title Text Block -->
<Style x:Key="SongTitleTextBlock" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource SongItemTitleBrush}"/>
<Setter Property="FontFamily" Value="{StaticResource AppFontFamilyMedium}"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontWeight" Value="Medium"/>
<Setter Property="LineStackingStrategy" Value="BlockLineHeight"/>
@@ -237,7 +238,7 @@
<Setter Property="Background" Value="Transparent"></Setter>
</Style>
</MenuFlyout.MenuFlyoutPresenterStyle>
<MenuFlyoutItem Text="Play" FontWeight="SemiBold" Command="{Binding PlaySongCommand}">
<MenuFlyoutItem Text="Play" FontFamily="{StaticResource AppFontFamilySemiBold}" FontWeight="SemiBold" Command="{Binding PlaySongCommand}">
<MenuFlyoutItem.Icon>
<PathIcon Data="{StaticResource PlayIcon}"></PathIcon>
</MenuFlyoutItem.Icon>

View File

@@ -25,20 +25,33 @@
<GradientStop Color="#0B0D0F" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ActiveGradientBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#E1B88A" Offset="0"/>
<GradientStop Color="#E9B578" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ActiveDarkGradientBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#F9EBC6" Offset="0"/>
<GradientStop Color="#A66831" Offset="1"/>
</LinearGradientBrush>
<!-- Playlist Icon Path -->
<Style x:Key="PlaylistIconPath" TargetType="PathIcon">
<Setter Property="Foreground" Value="{StaticResource PlaylistItemIconBrush}"/>
<Setter Property="Foreground" Value="{StaticResource ActiveDarkGradientBrush}"/>
</Style>
<Style x:Key="SelectedPlaylistIconPath" TargetType="PathIcon" BasedOn="{StaticResource PlaylistIconPath}">
<Setter Property="Foreground" Value="{StaticResource AccentTextFillColorPrimaryBrush}"/>
<Setter Property="Foreground" Value="{StaticResource ActiveGradientBrush}"/>
<Setter Property="Foreground" Value="{StaticResource ActiveDarkGradientBrush}"/>
</Style>
<!-- Playlist Title Text Block -->
<Style x:Key="PlaylistTitleTextBlock" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource PlaylistItemTitleBrush}"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontWeight" Value="Medium"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="LineStackingStrategy" Value="BlockLineHeight"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
@@ -47,6 +60,8 @@
<Style x:Key="SelectedPlaylistTitleTextBlock" TargetType="TextBlock" BasedOn="{StaticResource PlaylistTitleTextBlock}">
<Setter Property="Foreground" Value="{StaticResource AccentTextFillColorPrimaryBrush}"/>
<Setter Property="FontFamily" Value="{StaticResource AppFontFamilyMedium}"/>
<Setter Property="FontWeight" Value="Medium"/>
</Style>
<!-- Playlist Item Subtitle Text Block -->