This commit is contained in:
Zhe Fang
2025-07-20 23:28:37 -04:00
parent b649e9761d
commit 34bdbc89bc
45 changed files with 1524 additions and 515 deletions

View File

@@ -12,7 +12,7 @@
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.18.0" />
Version="1.0.20.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

View File

@@ -12,6 +12,7 @@
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
@@ -48,6 +49,7 @@
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
@@ -82,7 +84,7 @@
<Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="10,0" />
<Setter Property="Padding" Value="16,9,16,11" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
@@ -99,6 +101,374 @@
<Setter Property="CornerRadius" Value="6" />
</Style>
<Style x:Key="GhostSliderStyle" TargetType="Slider">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ManipulationMode" Value="None" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-7,0,-7,0" />
<Setter Property="IsFocusEngagementEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid Margin="{TemplateBinding Padding}">
<Grid.Resources>
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="0,1,1,0" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentPresenter
x:Name="HeaderContentPresenter"
Grid.Row="0"
Margin="{ThemeResource SliderTopHeaderMargin}"
x:DeferLoadStrategy="Lazy"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
Foreground="{ThemeResource SliderHeaderForeground}"
TextWrapping="Wrap"
Visibility="Collapsed" />
<Grid
x:Name="SliderContainer"
Grid.Row="1"
Background="{ThemeResource SliderContainerBackground}"
Control.IsTemplateFocusTarget="True">
<Grid x:Name="HorizontalTemplate" MinHeight="{ThemeResource SliderHorizontalHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
</Grid.RowDefinitions>
<Rectangle
x:Name="HorizontalTrackRect"
Grid.Row="1"
Grid.ColumnSpan="3"
Height="2"
Fill="{TemplateBinding Background}" />
<Rectangle
x:Name="HorizontalDecreaseRect"
Grid.Row="1"
Fill="{TemplateBinding Foreground}" />
<TickBar
x:Name="TopTickBar"
Grid.ColumnSpan="3"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="0,0,0,4"
VerticalAlignment="Bottom"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="HorizontalInlineTickBar"
Grid.Row="1"
Grid.ColumnSpan="3"
Height="2"
Fill="{ThemeResource SliderInlineTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="BottomTickBar"
Grid.Row="2"
Grid.ColumnSpan="3"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="0,4,0,0"
VerticalAlignment="Top"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<Thumb
x:Name="HorizontalThumb"
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1"
Width="2"
Height="2"
AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}"
FocusVisualMargin="-14,-6,-14,-6"
Style="{StaticResource SliderThumbStyle}" />
</Grid>
<Grid
x:Name="VerticalTemplate"
MinWidth="{ThemeResource SliderVerticalWidth}"
Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
</Grid.ColumnDefinitions>
<Rectangle
x:Name="VerticalTrackRect"
Grid.RowSpan="3"
Grid.Column="1"
Width="{ThemeResource SliderTrackThemeHeight}"
Fill="{TemplateBinding Background}" />
<Rectangle
x:Name="VerticalDecreaseRect"
Grid.Row="2"
Grid.Column="1"
Fill="{TemplateBinding Foreground}" />
<TickBar
x:Name="LeftTickBar"
Grid.RowSpan="3"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="0,0,4,0"
HorizontalAlignment="Right"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="VerticalInlineTickBar"
Grid.RowSpan="3"
Grid.Column="1"
Width="{ThemeResource SliderTrackThemeHeight}"
Fill="{ThemeResource SliderInlineTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="RightTickBar"
Grid.RowSpan="3"
Grid.Column="2"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="4,0,0,0"
HorizontalAlignment="Left"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<Thumb
x:Name="VerticalThumb"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Width="24"
Height="8"
AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}"
FocusVisualMargin="-6,-14,-6,-14"
Style="{StaticResource SliderThumbStyle}" />
</Grid>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TransparentSliderStyle" TargetType="Slider">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ManipulationMode" Value="None" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-7,0,-7,0" />
<Setter Property="IsFocusEngagementEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid Margin="{TemplateBinding Padding}">
<Grid.Resources>
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="0,1,1,0" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentPresenter
x:Name="HeaderContentPresenter"
Grid.Row="0"
Margin="{ThemeResource SliderTopHeaderMargin}"
x:DeferLoadStrategy="Lazy"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
Foreground="{ThemeResource SliderHeaderForeground}"
TextWrapping="Wrap"
Visibility="Collapsed" />
<Grid
x:Name="SliderContainer"
Grid.Row="1"
Background="{ThemeResource SliderContainerBackground}"
Control.IsTemplateFocusTarget="True">
<Grid x:Name="HorizontalTemplate" MinHeight="{ThemeResource SliderHorizontalHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
</Grid.RowDefinitions>
<Rectangle
x:Name="HorizontalTrackRect"
Grid.Row="1"
Grid.ColumnSpan="3"
Height="2"
Fill="{TemplateBinding Background}" />
<Rectangle
x:Name="HorizontalDecreaseRect"
Grid.Row="1"
Fill="{TemplateBinding Foreground}" />
<TickBar
x:Name="TopTickBar"
Grid.ColumnSpan="3"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="0,0,0,4"
VerticalAlignment="Bottom"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="HorizontalInlineTickBar"
Grid.Row="1"
Grid.ColumnSpan="3"
Height="2"
Fill="{ThemeResource SliderInlineTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="BottomTickBar"
Grid.Row="2"
Grid.ColumnSpan="3"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="0,4,0,0"
VerticalAlignment="Top"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<Thumb
x:Name="HorizontalThumb"
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1"
Width="2"
Height="2"
AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}"
FocusVisualMargin="-14,-6,-14,-6"
Style="{StaticResource SliderThumbStyle}" />
</Grid>
<Grid
x:Name="VerticalTemplate"
MinWidth="{ThemeResource SliderVerticalWidth}"
Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
</Grid.ColumnDefinitions>
<Rectangle
x:Name="VerticalTrackRect"
Grid.RowSpan="3"
Grid.Column="1"
Width="{ThemeResource SliderTrackThemeHeight}"
Fill="{TemplateBinding Background}" />
<Rectangle
x:Name="VerticalDecreaseRect"
Grid.Row="2"
Grid.Column="1"
Fill="{TemplateBinding Foreground}" />
<TickBar
x:Name="LeftTickBar"
Grid.RowSpan="3"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="0,0,4,0"
HorizontalAlignment="Right"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="VerticalInlineTickBar"
Grid.RowSpan="3"
Grid.Column="1"
Width="{ThemeResource SliderTrackThemeHeight}"
Fill="{ThemeResource SliderInlineTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="RightTickBar"
Grid.RowSpan="3"
Grid.Column="2"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="4,0,0,0"
HorizontalAlignment="Left"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<Thumb
x:Name="VerticalThumb"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Width="24"
Height="8"
AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}"
FocusVisualMargin="-6,-14,-6,-14"
Style="{StaticResource SliderThumbStyle}" />
</Grid>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />

View File

@@ -55,7 +55,7 @@ namespace BetterLyrics.WinUI3
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
WindowHelper.OpenOrShowWindow<LyricsWindow>();
WindowHelper.OpenWindow<LyricsWindow>();
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow == null) return;
@@ -85,13 +85,13 @@ namespace BetterLyrics.WinUI3
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
.AddSingleton<ITranslateService, TranslateService>()
// Manager
// ViewModels
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()
.AddSingleton<SystemTrayViewModel>()
.AddSingleton<SettingsPageViewModel>()
.AddSingleton<LyricsPageViewModel>()
.AddSingleton<MusicGalleryViewModel>()
.AddSingleton<LyricsRendererViewModel>()
.BuildServiceProvider()
);

View File

@@ -9,6 +9,7 @@
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
</PropertyGroup>
<ItemGroup>
<Compile Remove="ViewModels\Lyrics\**" />
@@ -22,6 +23,8 @@
<None Remove="Assets\Core14.profile.xml" />
<None Remove="Assets\Segoe Fluent Icons.ttf" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\MusicGalleryPage.xaml" />
<None Remove="Views\MusicGalleryWindow.xaml" />
<None Remove="Views\SettingsWindow.xaml" />
</ItemGroup>
<ItemGroup>
@@ -33,7 +36,8 @@
<ItemGroup>
<PackageReference Include="3v.EvtSource" Version="2.0.0" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250703-build.2173" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" Version="0.1.250703-build.2173" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
@@ -46,10 +50,10 @@
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.7" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
@@ -58,8 +62,8 @@
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.7" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="Vanara.PInvoke.CoreAudio" Version="4.1.6" />
@@ -68,7 +72,7 @@
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
<PackageReference Include="WinUIEx" Version="2.6.0" />
<PackageReference Include="z440.atl.core" Version="7.0.0" />
<PackageReference Include="z440.atl.core" Version="7.2.0" />
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\InAppLyricsRenderer.xaml">
@@ -89,6 +93,16 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Views\MusicGalleryWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\MusicGalleryPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\SettingsWindow.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -0,0 +1,30 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class SecondsToFormattedTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is double seconds)
{
return TimeSpan.FromSeconds(seconds).ToString(@"mm\:ss");
}
else if (value is int secondsInt)
{
return TimeSpan.FromSeconds(secondsInt).ToString(@"mm\:ss");
}
return value?.ToString() ?? "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -35,6 +35,17 @@ namespace BetterLyrics.WinUI3.Helper
return stream;
}
public static RandomAccessStreamReference ByteArrayToRandomAccessStreamReference(byte[] bytes)
{
var stream = new InMemoryRandomAccessStream();
var writer = new DataWriter(stream);
writer.WriteBytes(bytes);
writer.StoreAsync().GetAwaiter().GetResult();
writer.FlushAsync().GetAwaiter().GetResult();
writer.DetachStream();
return RandomAccessStreamReference.CreateFromStream(stream);
}
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(int width, int height)
{
var device = CanvasDevice.GetSharedDevice();

View File

@@ -31,11 +31,8 @@ namespace BetterLyrics.WinUI3.Helper
while (_activeWindows.Count > 0)
{
var window = (Window)_activeWindows[0];
DockModeHelper.Disable(window);
window.Close();
_activeWindows.Remove(window);
}
App.Current.Exit();
}
public static T? GetWindowByWindowType<T>()
@@ -49,33 +46,32 @@ namespace BetterLyrics.WinUI3.Helper
}
return default;
}
public static void OpenOrShowWindow<T>()
public static void OpenWindow<T>()
{
var window = _activeWindows.Find(w => w is T);
if (window != null)
if (window == null)
{
var castedWindow = (Window)window;
castedWindow.Restore();
}
else
{
object newWindow;
if (typeof(T) == typeof(LyricsWindow))
{
newWindow = new LyricsWindow();
((LyricsWindow)newWindow).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
window = new LyricsWindow();
((LyricsWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
}
else if (typeof(T) == typeof(SettingsWindow))
{
newWindow = new SettingsWindow();
window = new SettingsWindow();
}
else if (typeof(T) == typeof(MusicGalleryWindow))
{
window = new MusicGalleryWindow();
}
else
{
throw new ArgumentException("Unsupported window type", nameof(T));
}
((Window)newWindow).Activate();
TrackWindow(newWindow);
TrackWindow(window);
}
var castedWindow = (Window)window;
castedWindow.Restore();
}
public static void RestartApp(string args = "")
@@ -101,7 +97,19 @@ namespace BetterLyrics.WinUI3.Helper
private static void TrackWindow(object window)
{
if (!_activeWindows.Contains(window))
{
_activeWindows.Add(window);
var castedWindow = (Window)window;
castedWindow.Closed += WindowHelper_Closed;
}
}
private static void WindowHelper_Closed(object sender, WindowEventArgs args)
{
if (_activeWindows.Contains(sender))
{
_activeWindows.Remove(sender);
}
}
}
}

View File

@@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public partial class LocalLyricsFolder : ObservableObject
public partial class LocalMediaFolder : ObservableObject
{
[ObservableProperty]
public partial bool IsEnabled { get; set; }
@@ -12,9 +12,9 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty]
public partial string Path { get; set; }
public LocalLyricsFolder() { }
public LocalMediaFolder() { }
public LocalLyricsFolder(string path, bool isEnabled)
public LocalMediaFolder(string path, bool isEnabled)
{
Path = path;
IsEnabled = isEnabled;

View File

@@ -14,6 +14,9 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty]
public partial string Artist { get; set; }
[ObservableProperty]
public partial int? Duration { get; set; }
[ObservableProperty]
public partial double? DurationMs { get; set; }

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class TrimmedTrack
{
public string Title { get; set; }
public string Artist { get; set; }
public string Album { get; set; }
public int? Year { get; set; }
public string Genre { get; set; }
public string FilePath { get; set; }
public int Duration { get; set; }
public byte[]? AlbumArt { get; set; }
}
}

View File

@@ -11,7 +11,7 @@ namespace BetterLyrics.WinUI3.Serialization
[JsonSerializable(typeof(List<AlbumArtSearchProviderInfo>))]
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
[JsonSerializable(typeof(List<MediaSourceProviderInfo>))]
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
[JsonSerializable(typeof(List<LocalMediaFolder>))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(TranslateResponse))]
[JsonSerializable(typeof(JsonElement))]

View File

@@ -67,7 +67,7 @@ namespace BetterLyrics.WinUI3.Services
private byte[]? SearchFile(string artist, string album)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
foreach (var folder in _settingsService.LocalMediaFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{

View File

@@ -14,6 +14,6 @@ namespace BetterLyrics.WinUI3.Services
{
event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
public void UpdateWatchers(List<LocalLyricsFolder> folders);
public void UpdateWatchers(List<LocalMediaFolder> folders);
}
}

View File

@@ -19,6 +19,7 @@ namespace BetterLyrics.WinUI3.Services
Task PauseAsync();
Task PreviousAsync();
Task NextAsync();
Task ChangePosition(double seconds);
bool IsPlaying { get; }
SongInfo? SongInfo { get; }

View File

@@ -41,7 +41,7 @@ namespace BetterLyrics.WinUI3.Services
int PositionOffset { get; set; }
// Lyrics lib
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
List<LocalMediaFolder> LocalMediaFolders { get; set; }
// Lyrics style and effetc

View File

@@ -18,7 +18,7 @@ namespace BetterLyrics.WinUI3.Services
public LibWatcherService(ISettingsService settingsService)
{
_settingsService = settingsService;
UpdateWatchers(_settingsService.LocalLyricsFolders);
UpdateWatchers(_settingsService.LocalMediaFolders);
}
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
@@ -32,7 +32,7 @@ namespace BetterLyrics.WinUI3.Services
_watchers.Clear();
}
public void UpdateWatchers(List<LocalLyricsFolder> folders)
public void UpdateWatchers(List<LocalMediaFolder> folders)
{
// 移除不再监听的
foreach (var key in _watchers.Keys.ToList())

View File

@@ -164,7 +164,7 @@ namespace BetterLyrics.WinUI3.Services
private async Task<string?> SearchFile(string title, string artist, LyricsFormat format)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
foreach (var folder in _settingsService.LocalMediaFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
@@ -186,7 +186,7 @@ namespace BetterLyrics.WinUI3.Services
private string? SearchEmbedded(string title, string artist)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
foreach (var folder in _settingsService.LocalMediaFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{

View File

@@ -154,6 +154,8 @@ namespace BetterLyrics.WinUI3.Services
SourceAppUserModelId = id,
};
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
await _onAnyMediaPropertyChangedRunner.RunAsync(async token =>
{
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
@@ -367,6 +369,15 @@ namespace BetterLyrics.WinUI3.Services
}
}
public async Task ChangePosition(double seconds)
{
var focusedSession = _mediaManager.GetFocusedSession();
if (focusedSession != null)
{
await focusedSession.ControlSession.TryChangePlaybackPositionAsync(TimeSpan.FromSeconds(seconds).Ticks);
}
}
public void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message)
{
if (message.Sender is SettingsPageViewModel)

View File

@@ -231,7 +231,7 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(DockWindowHeightKey, 64); // 64px
SetDefault(SelectedFontFamilyIndexKey, 0);
SetDefault(LyricsFontFamilyKey, FontHelper.SystemFontFamilies.ElementAtOrDefault(0));
SetDefault(IsDragEverywhereEnabledKey, true);
SetDefault(IsDragEverywhereEnabledKey, false);
}
public bool IsDragEverywhereEnabled
@@ -420,19 +420,19 @@ namespace BetterLyrics.WinUI3.Services
set => SetValue(LanguageKey, (int)value);
}
public List<LocalLyricsFolder> LocalLyricsFolders
public List<LocalMediaFolder> LocalMediaFolders
{
get =>
System.Text.Json.JsonSerializer.Deserialize(
GetValue<string>(LocalLyricsFoldersKey) ?? "[]",
SourceGenerationContext.Default.ListLocalLyricsFolder
SourceGenerationContext.Default.ListLocalMediaFolder
)!;
set =>
SetValue(
LocalLyricsFoldersKey,
System.Text.Json.JsonSerializer.Serialize(
value,
SourceGenerationContext.Default.ListLocalLyricsFolder
SourceGenerationContext.Default.ListLocalMediaFolder
)
);
}

View File

@@ -793,4 +793,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
<value>Extend the title bar to the entire page so that the window can be dragged in any non-interactive area</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>Music gallery</value>
</data>
</root>

View File

@@ -793,4 +793,7 @@
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
<value>タイトルバーをページ全体に拡張して、ウィンドウを非対話領域でドラッグできるようにします</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>音楽ギャラリー</value>
</data>
</root>

View File

@@ -793,4 +793,7 @@
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
<value>비 중과 영역에서 창을 드래그 할 수 있도록 제목 표시 줄을 전체 페이지로 확장하십시오.</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>음악 갤러리</value>
</data>
</root>

View File

@@ -793,4 +793,7 @@
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
<value>将标题栏扩展至整个页面使得在任意非交互区域均可拖拽窗口</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>音乐库</value>
</data>
</root>

View File

@@ -793,4 +793,7 @@
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
<value>將標題列擴展至整個頁面使得在任意非互動區域均可拖曳窗口</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>音樂庫</value>
</data>
</root>

View File

@@ -9,16 +9,19 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using System;
using System.Numerics;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsPageViewModel : BaseViewModel,
IRecipient<PropertyChangedMessage<bool>>,
public partial class LyricsPageViewModel : BaseViewModel,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<string>>
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<TimeSpan>>
{
private readonly IPlaybackService _playbackService;
@@ -46,10 +49,10 @@ namespace BetterLyrics.WinUI3.ViewModels
IsSongPlaying = _playbackService.IsPlaying;
}
//private void SystemVolumeHelper_VolumeChanged(int volume)
//{
// Volume = volume;
//}
private void SystemVolumeHelper_VolumeChanged(int volume)
{
Volume = volume;
}
private void PlaybackService_IsPlayingChanged(object? sender, Events.IsPlayingChangedEventArgs e)
{
@@ -59,14 +62,21 @@ namespace BetterLyrics.WinUI3.ViewModels
private void PlaybackService_SongInfoChanged(object? sender, Events.SongInfoChangedEventArgs e)
{
SongInfo = e.SongInfo;
SongDurationSeconds = SongInfo?.Duration ?? 0;
if (ResetPositionOffsetOnSongChanged)
{
PositionOffset = 0;
}
}
//[ObservableProperty]
//public partial int Volume { get; set; }
[ObservableProperty]
public partial double TimelinePositionSeconds { get; set; }
[ObservableProperty]
public partial int SongDurationSeconds { get; set; }
[ObservableProperty]
public partial int Volume { get; set; }
[ObservableProperty]
public partial string LyricsFontFamily { get; set; }
@@ -74,12 +84,6 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial int LyricsFontSize { get; set; }
[ObservableProperty]
public partial Vector3 BottomRightCommandGridTranslation { get; set; } = new Vector3(0, 0, 0);
[ObservableProperty]
public partial Vector3 BottomCenterCommandGridTranslation { get; set; } = new Vector3(0, 0, 0);
[ObservableProperty]
public partial bool IsImmersiveMode { get; set; }
@@ -87,7 +91,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial float BottomCommandGridOpacity { get; set; }
[ObservableProperty]
public partial Thickness BottomCommandGridMargin { get; set; } = new Thickness(12);
public partial float BottomCommandFlyoutTriggerOpacity { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
@@ -157,7 +161,7 @@ namespace BetterLyrics.WinUI3.ViewModels
[RelayCommand]
private static void OpenSettingsWindow()
{
WindowHelper.OpenOrShowWindow<SettingsWindow>();
WindowHelper.OpenWindow<SettingsWindow>();
}
[RelayCommand]
@@ -205,10 +209,12 @@ namespace BetterLyrics.WinUI3.ViewModels
if (value)
{
BottomCommandGridOpacity = 0f;
BottomCommandFlyoutTriggerOpacity = 0f;
}
else
{
BottomCommandGridOpacity = 1f;
BottomCommandFlyoutTriggerOpacity = 1f;
}
}
@@ -239,9 +245,23 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
//partial void OnVolumeChanged(int value)
//{
// SystemVolumeHelper.SetMasterVolume(value);
//}
partial void OnVolumeChanged(int value)
{
SystemVolumeHelper.SetMasterVolume(value);
}
public void Receive(PropertyChangedMessage<TimeSpan> message)
{
if (message.Sender is LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.TotalTime))
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
TimelinePositionSeconds = message.NewValue.TotalSeconds;
});
}
}
}
}
}

View File

@@ -2,6 +2,7 @@
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
namespace BetterLyrics.WinUI3.ViewModels
{
@@ -66,6 +67,8 @@ namespace BetterLyrics.WinUI3.ViewModels
_playbackService.AlbumArtChangedChanged += PlaybackService_AlbumArtChangedChanged;
_playbackService.PositionChanged += PlaybackService_PositionChanged;
_isPlaying = _playbackService.IsPlaying;
UpdateColorConfig();
}
}

View File

@@ -78,7 +78,7 @@ namespace BetterLyrics.WinUI3.ViewModels
$"[DEBUG]\n" +
$"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
$"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
$"Cur time {_totalTime + _positionOffset}\n" +
$"Cur time {TotalTime + _positionOffset}\n" +
$"Lang size {_lyricsDataArr.Count}\n" +
$"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
new Vector2(10, 10),

View File

@@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
@@ -24,13 +25,13 @@ namespace BetterLyrics.WinUI3.ViewModels
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<EasingType>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>>>
IRecipient<PropertyChangedMessage<ObservableCollection<LocalMediaFolder>>>
{
public void Receive(PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message)
public void Receive(PropertyChangedMessage<ObservableCollection<LocalMediaFolder>> message)
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LocalLyricsFolders))
if (message.PropertyName == nameof(SettingsPageViewModel.LocalMediaFolders))
{
// Music lib changed, re-fetch lyrics
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
@@ -333,5 +334,6 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
}
}
}

View File

@@ -7,6 +7,8 @@ using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using Windows.UI;
@@ -29,7 +31,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (_isPlaying)
{
_totalTime += _elapsedTime;
TotalTime += _elapsedTime;
}
var playingLineIndex = GetCurrentPlayingLineIndex();
@@ -194,48 +196,13 @@ namespace BetterLyrics.WinUI3.ViewModels
_canvasYScrollTransition.Update(_elapsedTime);
int startVisibleLineIndex = -1;
int endVisibleLineIndex = -1;
// Update visible line indices
for (int i = startLineIndex; i <= endLineIndex; i++)
{
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
var lines = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines;
if (lines == null || lines.Count == 0) return;
if (line == null || line.CanvasTextLayout == null)
{
continue;
}
var textLayout = line.CanvasTextLayout;
if (
_canvasYScrollTransition.Value
+ _canvasHeight / 2
+ line.Position.Y
+ textLayout.LayoutBounds.Height
>= 0
)
{
if (startVisibleLineIndex == -1)
{
startVisibleLineIndex = i;
}
}
if (
_canvasYScrollTransition.Value
+ _canvasHeight / 2
+ line.Position.Y
+ textLayout.LayoutBounds.Height
>= control.Size.Height
)
{
if (endVisibleLineIndex == -1)
{
endVisibleLineIndex = i;
}
}
}
float offset = _canvasYScrollTransition.Value + _canvasHeight / 2;
int startVisibleLineIndex = FindFirstVisibleLine(lines, offset);
int endVisibleLineIndex = FindLastVisibleLine(lines, offset, _canvasHeight);
if (startVisibleLineIndex != -1 && endVisibleLineIndex == -1)
{
@@ -248,6 +215,52 @@ namespace BetterLyrics.WinUI3.ViewModels
_endVisibleLineIndex = endVisibleLineIndex;
}
private int FindFirstVisibleLine(IList<LyricsLine> lines, float offset)
{
int left = 0, right = lines.Count - 1, result = -1;
while (left <= right)
{
int mid = (left + right) / 2;
var line = lines[mid];
var layout = line.CanvasTextLayout;
if (layout == null) break;
float value = offset + line.Position.Y + (float)layout.LayoutBounds.Height;
if (value >= 0)
{
result = mid;
right = mid - 1;
}
else
{
left = mid + 1;
}
}
return result;
}
private int FindLastVisibleLine(IList<LyricsLine> lines, float offset, float canvasHeight)
{
int left = 0, right = lines.Count - 1, result = -1;
while (left <= right)
{
int mid = (left + right) / 2;
var line = lines[mid];
var layout = line.CanvasTextLayout;
if (layout == null) break;
float value = offset + line.Position.Y + (float)layout.LayoutBounds.Height;
if (value >= canvasHeight)
{
result = mid;
right = mid - 1;
}
else
{
left = mid + 1;
}
}
return result;
}
private void UpdateColorConfig()
{
if (_isDesktopMode || _isDockMode)

View File

@@ -31,7 +31,11 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial class LyricsRendererViewModel : BaseViewModel
{
private TimeSpan _elapsedTime = TimeSpan.Zero;
private TimeSpan _totalTime = TimeSpan.Zero;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
private TimeSpan _positionOffset = TimeSpan.Zero;
private int _songDurationMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
@@ -193,7 +197,7 @@ namespace BetterLyrics.WinUI3.ViewModels
private int GetCurrentPlayingLineIndex()
{
var totalMs = _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds;
var totalMs = TotalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds;
if (totalMs < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.FirstOrDefault()?.StartMs) return 0;
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
@@ -229,7 +233,7 @@ namespace BetterLyrics.WinUI3.ViewModels
else if (nextLine != null) lineEndMs = nextLine.StartMs;
else lineEndMs = _songDurationMs;
float now = (float)_totalTime.TotalMilliseconds + (float)_positionOffset.TotalMilliseconds;
float now = (float)TotalTime.TotalMilliseconds + (float)_positionOffset.TotalMilliseconds;
// 1. 还没到本句
if (now < line.StartMs)
@@ -333,9 +337,9 @@ namespace BetterLyrics.WinUI3.ViewModels
private void PlaybackService_PositionChanged(object? sender, PositionChangedEventArgs e)
{
if (Math.Abs(_totalTime.TotalMilliseconds - e.Position.TotalMilliseconds) >= _timelineSyncThreshold)
if (Math.Abs(TotalTime.TotalMilliseconds - e.Position.TotalMilliseconds) >= _timelineSyncThreshold)
{
_totalTime = e.Position;
TotalTime = e.Position;
}
}
@@ -362,7 +366,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{
await RefreshLyricsAsync(token);
});
_totalTime = TimeSpan.Zero;
TotalTime = TimeSpan.Zero;
}
}

View File

@@ -0,0 +1,148 @@
using ATL;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Media;
using Windows.Media.Core;
using Windows.Media.Playback;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class MusicGalleryViewModel : BaseViewModel,
IRecipient<PropertyChangedMessage<ObservableCollection<LocalMediaFolder>>>
{
private readonly ILibWatcherService _libWatcherService;
private readonly MediaPlayer _mediaPlayer = new();
private readonly SystemMediaTransportControls _smtc;
[ObservableProperty]
public partial ObservableCollection<Track> Tracks { get; set; } = [];
[ObservableProperty]
public partial bool IsDataLoading { get; set; } = false;
public MusicGalleryViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService) : base(settingsService)
{
_smtc = _mediaPlayer.SystemMediaTransportControls;
_mediaPlayer.CommandManager.IsEnabled = false;
_smtc.IsEnabled = true;
_smtc.IsPlayEnabled = true;
_smtc.IsPauseEnabled = true;
_smtc.IsNextEnabled = true;
_smtc.IsPreviousEnabled = true;
_smtc.ButtonPressed += Smtc_ButtonPressed;
_smtc.PlaybackPositionChangeRequested += Smtc_PlaybackPositionChangeRequested;
_libWatcherService = libWatcherService;
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
}
private void Smtc_PlaybackPositionChangeRequested(SystemMediaTransportControls sender, PlaybackPositionChangeRequestedEventArgs args)
{
_mediaPlayer.TimelineController.Position = args.RequestedPlaybackPosition;
}
private void Smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
_mediaPlayer.Play();
break;
case SystemMediaTransportControlsButton.Pause:
_smtc.PlaybackStatus = MediaPlaybackStatus.Paused;
_mediaPlayer.Pause();
break;
case SystemMediaTransportControlsButton.Next:
//Next
break;
case SystemMediaTransportControlsButton.Previous:
//Previous
break;
}
}
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, Events.LibChangedEventArgs e)
{
RefreshSongs();
}
public void RefreshSongs()
{
IsDataLoading = true;
Tracks.Clear();
Task.Run(() =>
{
foreach (var folder in _settingsService.LocalMediaFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
{
Track track = new(file);
_dispatcherQueue.TryEnqueue(() =>
{
Tracks.Add(track);
});
}
}
}
_dispatcherQueue.TryEnqueue(() =>
{
IsDataLoading = false;
});
});
}
public void PlaySongAt(int? index)
{
if (index.HasValue)
{
var track = Tracks.ElementAtOrDefault(index.Value);
if (track != null)
{
_mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(track.Path));
var updater = _smtc.DisplayUpdater;
updater.AppMediaId = Package.Current.Id.FullName;
updater.Type = MediaPlaybackType.Music;
updater.MusicProperties.Title = track.Title;
updater.MusicProperties.Artist = track.Artist;
updater.MusicProperties.AlbumTitle = track.Album;
if (track.EmbeddedPictures.FirstOrDefault()?.PictureData is byte[] pictureData)
{
updater.Thumbnail = ImageHelper.ByteArrayToRandomAccessStreamReference(pictureData);
}
updater.Update();
_mediaPlayer.Play();
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
}
}
}
public void Receive(PropertyChangedMessage<ObservableCollection<LocalMediaFolder>> message)
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LocalMediaFolders))
{
RefreshSongs();
}
}
}
}
}

View File

@@ -7,25 +7,17 @@ using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using ShadowViewer.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Globalization;
using Windows.Media.Playback;
using Windows.System;
using Windows.UI;
using Windows.UI.Popups;
using WinRT.Interop;
using MetadataHelper = BetterLyrics.WinUI3.Helper.MetadataHelper;
@@ -48,7 +40,7 @@ namespace BetterLyrics.WinUI3.ViewModels
LibreTranslateServer = _settingsService.LibreTranslateServer;
SelectedTargetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
LocalMediaFolders = [.. _settingsService.LocalMediaFolders];
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
AlbumArtSearchProvidersInfo = [.. _settingsService.AlbumArtSearchProvidersInfo];
@@ -180,7 +172,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial Enums.Language Language { get; set; }
[ObservableProperty]
public partial ObservableCollection<LocalLyricsFolder> LocalLyricsFolders { get; set; }
public partial ObservableCollection<LocalMediaFolder> LocalMediaFolders { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
@@ -345,18 +337,18 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
public void RemoveFolderAsync(LocalLyricsFolder folder)
public void RemoveFolderAsync(LocalMediaFolder folder)
{
LocalLyricsFolders.Remove(folder);
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
LocalMediaFolders.Remove(folder);
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
_libWatcherService.UpdateWatchers([.. LocalMediaFolders]);
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
}
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
public void ToggleLocalLyricsFolder(LocalMediaFolder folder)
{
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
}
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
@@ -392,16 +384,16 @@ namespace BetterLyrics.WinUI3.ViewModels
{
var normalizedPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
if (LocalLyricsFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
if (LocalMediaFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"));
}
else if (LocalLyricsFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
else if (LocalMediaFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
{
// 添加的文件夹是现有文件夹的子文件夹
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"));
}
else if (LocalLyricsFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
else if (LocalMediaFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
)
{
// 添加的文件夹是现有文件夹的父文件夹
@@ -409,10 +401,10 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
LocalLyricsFolders.Add(new LocalLyricsFolder(path, true));
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
LocalMediaFolders.Add(new LocalMediaFolder(path, true));
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
_libWatcherService.UpdateWatchers([.. LocalMediaFolders]);
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
}
}

View File

@@ -40,7 +40,7 @@ namespace BetterLyrics.WinUI3.ViewModels
[RelayCommand]
private static void OpenSettings()
{
WindowHelper.OpenOrShowWindow<SettingsWindow>();
WindowHelper.OpenWindow<SettingsWindow>();
}
[RelayCommand]

View File

@@ -46,26 +46,15 @@
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=RootGrid, Path=ActualHeight, Mode=OneWay}"
ComparisonCondition="LessThanOrEqual"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=RootGrid, Path=ActualHeight, Mode=OneWay}"
ComparisonCondition="GreaterThan"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
<!-- Bottom command area -->
<Grid
x:Name="BottomCommandGrid"
Margin="{x:Bind ViewModel.BottomCommandGridMargin, Mode=OneWay}"
Margin="12"
VerticalAlignment="Bottom"
Background="Transparent"
Opacity="{x:Bind ViewModel.BottomCommandGridOpacity, Mode=OneWay}"
PointerEntered="BottomCommandGrid_PointerEntered"
PointerExited="BottomCommandGrid_PointerExited">
@@ -73,291 +62,330 @@
<ScalarTransition />
</Grid.OpacityTransition>
<Grid Padding="3" HorizontalAlignment="Left">
<StackPanel
x:Name="BottomLeftCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<!--<Button Style="{StaticResource GhostButtonStyle}">
<Grid>
-->
<!-- Volumn: 0 -->
<!--
<FontIcon
x:Name="VolumeLevel0"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE74F;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
-->
<!-- Volumn: 1-32 -->
<!--
<FontIcon
x:Name="VolumeLevel1"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE993;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
-->
<!-- Volumn: 33-65 -->
<!--
<FontIcon
x:Name="VolumeLevel2"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE994;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
-->
<!-- Volumn: 66-100 -->
<!--
<FontIcon
x:Name="VolumeLevel3"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE995;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
</Grid>
<Button.Flyout>
<Flyout>
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.Volume, Mode=OneWay}" />
<TextBlock Margin="0,0,14,0" VerticalAlignment="Center" />
<Slider
Width="150"
Maximum="100"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="None"
Value="{x:Bind ViewModel.Volume, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>-->
</StackPanel>
</Grid>
<Grid x:Name="BottomCommandContent">
<Grid Padding="3" HorizontalAlignment="Left">
<StackPanel
x:Name="BottomLeftCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<Grid
Padding="3"
HorizontalAlignment="Center"
Style="{StaticResource CardGridStyle}"
Translation="{x:Bind ViewModel.BottomCenterCommandGridTranslation, Mode=OneWay}">
<Grid.TranslationTransition>
<Vector3Transition />
</Grid.TranslationTransition>
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.BottomCommandGridMargin.Bottom, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.BottomCommandGridMargin.Bottom, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<StackPanel
Margin="0,0,0,2"
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="2">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{Binding ElementName=TimelineSlider, Path=Value, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
<TextBlock Text="{Binding ElementName=TimelineSlider, Path=Maximum, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
</StackPanel>
<!-- Position offset -->
<Button Click="TimelineOffsetButton_Click" Style="{StaticResource GhostButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xECE7;"
RenderTransformOrigin="0.5,0.5">
<FontIcon.RenderTransform>
<RotateTransform Angle="90" CenterX="0.5" CenterY="0.5" />
</FontIcon.RenderTransform>
</FontIcon>
<ToolTipService.ToolTip>
<ToolTip x:Name="TimelineOffsetToolTip" x:Uid="LyricsPageTimelineOffsetButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Flyout x:Name="TimelineOffsetFlyout" ShouldConstrainToRootBounds="False">
<StackPanel>
<Slider
x:Uid="MainPagePositionOffsetSlider"
Maximum="5000"
Minimum="-5000"
SnapsTo="Ticks"
StepFrequency="100"
TickFrequency="100"
TickPlacement="Outside"
Value="{x:Bind ViewModel.PositionOffset, Mode=TwoWay}" />
<RelativePanel>
<TextBlock
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Text="{x:Bind ViewModel.PositionOffset, Mode=OneWay}" />
<Button
Click="PositionOffsetResetButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE777;}"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Style="{StaticResource GhostButtonStyle}" />
</RelativePanel>
<CheckBox IsChecked="{x:Bind ViewModel.ResetPositionOffsetOnSongChanged, Mode=TwoWay}">
<TextBlock x:Uid="LyricsPagePositionOffsetHint" />
</CheckBox>
</StackPanel>
</Flyout>
</Button.DataContext>
</Button>
</StackPanel>
</Grid>
<StackPanel
x:Name="BottomCenterCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<Button
Command="{x:Bind ViewModel.PreviousSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE622;}"
Style="{StaticResource GhostButtonStyle}" />
<Button
Command="{x:Bind ViewModel.PauseSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF8AE;}"
Style="{StaticResource GhostButtonStyle}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button
Command="{x:Bind ViewModel.PlaySongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF5B0;}"
Style="{StaticResource GhostButtonStyle}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button
Command="{x:Bind ViewModel.NextSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE623;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
<Grid Padding="3" HorizontalAlignment="Center">
<StackPanel
x:Name="BottomCenterCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<Button
Command="{x:Bind ViewModel.PreviousSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE622;}"
Style="{StaticResource GhostButtonStyle}" />
<Button
Command="{x:Bind ViewModel.PauseSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF8AE;}"
Style="{StaticResource GhostButtonStyle}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button
Command="{x:Bind ViewModel.PlaySongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF5B0;}"
Style="{StaticResource GhostButtonStyle}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button
Command="{x:Bind ViewModel.NextSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE623;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</Grid>
<Grid Padding="3" HorizontalAlignment="Right">
<StackPanel
x:Name="BottomRightCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<!-- Volume -->
<Button
Click="VolumeButton_Click"
Style="{StaticResource GhostButtonStyle}"
Visibility="Collapsed">
<Grid>
<!-- Volumn: 0 -->
<FontIcon
x:Name="VolumeLevel0"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE74F;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<!-- Volumn: 1-32 -->
<FontIcon
x:Name="VolumeLevel1"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE993;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<!-- Volumn: 33-65 -->
<FontIcon
x:Name="VolumeLevel2"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE994;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<!-- Volumn: 66-100 -->
<FontIcon
x:Name="VolumeLevel3"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE995;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
</Grid>
<Button.DataContext>
<Flyout x:Name="VolumeFlyout" ShouldConstrainToRootBounds="False">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.Volume, Mode=OneWay}" />
<TextBlock Margin="0,0,14,0" VerticalAlignment="Center" />
<Slider
Width="150"
Maximum="100"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="None"
Value="{x:Bind ViewModel.Volume, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.DataContext>
</Button>
<!-- Translation -->
<Button
Click="TranslationButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8BD;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Flyout x:Name="TranslationFlyout" ShouldConstrainToRootBounds="False">
<StackPanel>
<ToggleSwitch x:Uid="LyricsPageTranslationEnabled" IsOn="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}" />
<ToggleSwitch
x:Uid="LyricsPageTranslationOnly"
IsEnabled="{x:Bind ViewModel.IsTranslationEnabled, Mode=OneWay}"
IsOn="{x:Bind ViewModel.ShowTranslationOnly, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.DataContext>
</Button>
<!-- Display type -->
<Button
x:Name="DisplayTypeSwitchButton"
x:Uid="MainPageDisplayTypeSwitcher"
Click="DisplayTypeSwitchButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF246;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="PresentationTypeToolTip" x:Uid="LyricsPageDisplayTypeButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Flyout x:Name="DisplayTypeSwitchFlyout" ShouldConstrainToRootBounds="false">
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="Padding" Value="12,2,12,8" />
<Setter Property="CornerRadius" Value="8" />
</Style>
</Flyout.FlyoutPresenterStyle>
<RadioButtons MaxColumns="1" SelectedIndex="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}">
<RadioButton x:Uid="MainPageAlbumArtOnly" Click="AlbumArtOnlyRadioButton_Click" />
<RadioButton x:Uid="MainPageLyriscOnly" Click="LyricsOnlyRadioButton_Click" />
<RadioButton x:Uid="MainPageSplitView" Click="SplitViewRadioButton_Click" />
</RadioButtons>
</Flyout>
</Button.DataContext>
</Button>
<!-- Settings -->
<Button
x:Name="SettingsButton"
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF8B0;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
<Slider
x:Name="TimelineSlider"
Margin="0,-32,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Maximum="{x:Bind ViewModel.SongDurationSeconds, Mode=OneWay}"
Minimum="0"
Style="{StaticResource GhostSliderStyle}"
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
Value="{x:Bind ViewModel.TimelinePositionSeconds, Mode=OneWay}" />
<Slider
x:Name="TimelineSliderOverlay"
Margin="0,-32,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Maximum="{x:Bind ViewModel.SongDurationSeconds, Mode=OneWay}"
Minimum="0"
Style="{StaticResource TransparentSliderStyle}"
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
ValueChanged="TimelineSliderOverlay_ValueChanged" />
</Grid>
</Grid>
<!-- Bottom command flyout trigger -->
<Grid
x:Name="BottomCommandFlyoutTrigger"
Height="12"
VerticalAlignment="Bottom"
Background="Transparent"
CornerRadius="3,3,0,0"
Opacity="{x:Bind ViewModel.BottomCommandFlyoutTriggerOpacity, Mode=OneWay}"
PointerEntered="BottomCommandFlyoutTrigger_PointerEntered"
PointerExited="BottomCommandFlyoutTrigger_PointerExited"
Tapped="BottomCommandFlyoutTrigger_Tapped">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<Grid
Padding="3"
HorizontalAlignment="Right"
Style="{StaticResource CardGridStyle}"
Translation="{x:Bind ViewModel.BottomRightCommandGridTranslation, Mode=OneWay}">
x:Name="BottomCommandFlyoutTriggerHint"
Width="150"
Margin="4"
Background="{ThemeResource TextFillColorPrimaryBrush}"
CornerRadius="2"
Translation="0,0,0">
<Grid.TranslationTransition>
<Vector3Transition />
</Grid.TranslationTransition>
<StackPanel
x:Name="BottomRightCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<!-- Position offset -->
<Button Click="TimelineOffsetButton_Click" Style="{StaticResource GhostButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xECE7;"
RenderTransformOrigin="0.5,0.5">
<FontIcon.RenderTransform>
<RotateTransform Angle="90" CenterX="0.5" CenterY="0.5" />
</FontIcon.RenderTransform>
</FontIcon>
<ToolTipService.ToolTip>
<ToolTip x:Name="TimelineOffsetToolTip" x:Uid="LyricsPageTimelineOffsetButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Flyout x:Name="TimelineOffsetFlyout" ShouldConstrainToRootBounds="False">
<StackPanel>
<Slider
x:Uid="MainPagePositionOffsetSlider"
Maximum="5000"
Minimum="-5000"
SnapsTo="Ticks"
StepFrequency="100"
TickFrequency="100"
TickPlacement="Outside"
Value="{x:Bind ViewModel.PositionOffset, Mode=TwoWay}" />
<RelativePanel>
<TextBlock
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Text="{x:Bind ViewModel.PositionOffset, Mode=OneWay}" />
<Button
Click="PositionOffsetResetButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE777;}"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Style="{StaticResource GhostButtonStyle}" />
</RelativePanel>
<CheckBox IsChecked="{x:Bind ViewModel.ResetPositionOffsetOnSongChanged, Mode=TwoWay}">
<TextBlock x:Uid="LyricsPagePositionOffsetHint" />
</CheckBox>
</StackPanel>
</Flyout>
</Button.DataContext>
</Button>
<!-- Translation -->
<Button
Click="TranslationButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8BD;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Flyout x:Name="TranslationFlyout" ShouldConstrainToRootBounds="False">
<StackPanel>
<ToggleSwitch x:Uid="LyricsPageTranslationEnabled" IsOn="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}" />
<ToggleSwitch
x:Uid="LyricsPageTranslationOnly"
IsEnabled="{x:Bind ViewModel.IsTranslationEnabled, Mode=OneWay}"
IsOn="{x:Bind ViewModel.ShowTranslationOnly, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.DataContext>
</Button>
<!-- Display type -->
<Button
x:Name="DisplayTypeSwitchButton"
x:Uid="MainPageDisplayTypeSwitcher"
Click="DisplayTypeSwitchButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF246;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="PresentationTypeToolTip" x:Uid="LyricsPageDisplayTypeButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Flyout x:Name="DisplayTypeSwitchFlyout" ShouldConstrainToRootBounds="false">
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="Padding" Value="12,2,12,8" />
<Setter Property="CornerRadius" Value="8" />
</Style>
</Flyout.FlyoutPresenterStyle>
<RadioButtons MaxColumns="1" SelectedIndex="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}">
<RadioButton x:Uid="MainPageAlbumArtOnly" Click="AlbumArtOnlyRadioButton_Click" />
<RadioButton x:Uid="MainPageLyriscOnly" Click="LyricsOnlyRadioButton_Click" />
<RadioButton x:Uid="MainPageSplitView" Click="SplitViewRadioButton_Click" />
</RadioButtons>
</Flyout>
</Button.DataContext>
</Button>
<!-- Settings -->
<Button
x:Name="SettingsButton"
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF8B0;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
<Grid.ContextFlyout>
<Flyout x:Name="BottomCommandFlyout" ShouldConstrainToRootBounds="False">
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="MinWidth" Value="600" />
<Setter Property="MinHeight" Value="100" />
<Setter Property="CornerRadius" Value="12" />
</Style>
</Flyout.FlyoutPresenterStyle>
<Grid x:Name="BottomCommandFlyoutContainer" VerticalAlignment="Bottom" />
</Flyout>
</Grid.ContextFlyout>
</Grid>
<TeachingTip
@@ -367,10 +395,10 @@
IsOpen="{x:Bind ViewModel.IsWelcomeTeachingTipOpen, Mode=OneWay}"
Target="{x:Bind RootGrid}" />
<uc:SystemTray />
<!--<uc:SystemTray />-->
<VisualStateManager.VisualStateGroups>
<!--<VisualStateGroup x:Name="VolumeState">
<VisualStateGroup x:Name="VolumeState">
<VisualState x:Name="Volume0">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
@@ -427,7 +455,7 @@
<Setter Target="VolumeLevel3.Opacity" Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>-->
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Page>

View File

@@ -6,6 +6,8 @@ using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
@@ -14,16 +16,17 @@ namespace BetterLyrics.WinUI3.Views
public sealed partial class LyricsPage : Page
{
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly IPlaybackService _playbackService = Ioc.Default.GetRequiredService<IPlaybackService>();
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
public LyricsPage()
{
this.InitializeComponent();
DataContext = Ioc.Default.GetService<LyricsPageViewModel>();
DataContext = Ioc.Default.GetRequiredService<LyricsPageViewModel>();
}
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
private void WelcomeTeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args)
{
ViewModel.IsFirstRun = false;
@@ -54,18 +57,20 @@ namespace BetterLyrics.WinUI3.Views
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (ViewModel.IsImmersiveMode)
if (ViewModel.IsImmersiveMode && BottomCommandGrid.Children.Count != 0)
{
ViewModel.BottomCommandGridOpacity = 1f;
}
e.Handled = true;
}
private void BottomCommandGrid_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (ViewModel.IsImmersiveMode)
if (ViewModel.IsImmersiveMode && BottomCommandGrid.Children.Count != 0)
{
ViewModel.BottomCommandGridOpacity = 0f;
}
e.Handled = true;
}
private void DisplayTypeSwitchButton_Click(object sender, RoutedEventArgs e)
@@ -75,7 +80,7 @@ namespace BetterLyrics.WinUI3.Views
private void TimelineOffsetButton_Click(object sender, RoutedEventArgs e)
{
TimelineOffsetFlyout.ShowAt(BottomRightCommandStackPanel);
TimelineOffsetFlyout.ShowAt(BottomLeftCommandStackPanel);
}
private void TranslationButton_Click(object sender, RoutedEventArgs e)
@@ -85,33 +90,59 @@ namespace BetterLyrics.WinUI3.Views
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width < 500)
if (e.NewSize.Width < 500 || e.NewSize.Height < 100)
{
ViewModel.BottomCenterCommandGridTranslation = new System.Numerics.Vector3(0, -48, 0);
if (BottomCommandGrid.Children.Count != 0)
{
BottomCommandGrid.Children.Remove(BottomCommandContent);
BottomCommandFlyoutContainer.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 0, 0);
}
else
{
ViewModel.BottomCenterCommandGridTranslation = new System.Numerics.Vector3(0, 0, 0);
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
BottomCommandFlyout.Hide();
BottomCommandFlyoutContainer.Children.Remove(BottomCommandContent);
BottomCommandGrid.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 12, 0);
}
}
if (e.NewSize.Height < 80)
{
ViewModel.BottomRightCommandGridTranslation = new System.Numerics.Vector3(-200, 0, 0);
}
else
{
ViewModel.BottomRightCommandGridTranslation = new System.Numerics.Vector3(0, 0, 0);
}
private async void TimelineSliderOverlay_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
await _playbackService.ChangePosition(e.NewValue);
}
if (e.NewSize.Height < 100)
{
ViewModel.BottomCommandGridMargin = new Thickness(0);
}
else
{
ViewModel.BottomCommandGridMargin = new Thickness(12);
}
private void VolumeButton_Click(object sender, RoutedEventArgs e)
{
VolumeFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void BottomCommandFlyoutTrigger_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (ViewModel.IsImmersiveMode && BottomCommandFlyoutContainer.Children.Count != 0)
{
ViewModel.BottomCommandFlyoutTriggerOpacity = 1f;
}
}
private void BottomCommandFlyoutTrigger_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (ViewModel.IsImmersiveMode && BottomCommandFlyoutContainer.Children.Count != 0)
{
ViewModel.BottomCommandFlyoutTriggerOpacity = 0f;
}
}
private void BottomCommandFlyoutTrigger_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
BottomCommandFlyout.ShowAt(BottomCommandFlyoutTrigger);
}
}
}
}

View File

@@ -25,7 +25,6 @@
<!-- Top command -->
<Grid
x:Name="TopCommandGrid"
Margin="12"
VerticalAlignment="Top"
Opacity="{x:Bind ViewModel.TopCommandGridOpacity, Mode=OneWay}"
PointerEntered="TopCommandGrid_PointerEntered"
@@ -34,10 +33,7 @@
<ScalarTransition />
</Grid.OpacityTransition>
<Grid
Padding="3"
HorizontalAlignment="Left"
Style="{StaticResource CardGridStyle}">
<Grid Padding="3" HorizontalAlignment="Left">
<StackPanel
x:Name="TopLeftCommandStackPanel"
HorizontalAlignment="Left"
@@ -46,6 +42,18 @@
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<!-- Music gallery -->
<Button
Click="MusicGalleryButton_Click"
Style="{StaticResource TitleBarButtonStyle}"
Visibility="Collapsed">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize}"
Glyph="&#xE8A9;" />
</Button>
<!-- Immersive mode -->
<ToggleButton
x:Name="ImmersiveButton"
Command="{x:Bind ViewModel.ImmersiveToggleButtonEnabledChangedCommand}"
@@ -59,10 +67,7 @@
</StackPanel>
</Grid>
<Grid
Padding="3"
HorizontalAlignment="Right"
Style="{StaticResource CardGridStyle}">
<Grid Padding="3" HorizontalAlignment="Right">
<StackPanel
x:Name="TopRightCommandStackPanel"
Orientation="Horizontal"

View File

@@ -109,25 +109,27 @@ namespace BetterLyrics.WinUI3.Views
var rect = AppWindow.Position;
var size = AppWindow.Size;
if (ViewModel.IsDesktopMode)
{
_settingsService.DesktopWindowLeft = rect.X;
_settingsService.DesktopWindowTop = rect.Y;
_settingsService.DesktopWindowWidth = size.Width;
_settingsService.DesktopWindowHeight = size.Height;
}
else if (ViewModel.IsDockMode)
if (rect.X >= 0 && rect.Y >= 0 && size.Width > 0 && size.Height > 0)
{
if (ViewModel.IsDesktopMode)
{
_settingsService.DesktopWindowLeft = rect.X;
_settingsService.DesktopWindowTop = rect.Y;
_settingsService.DesktopWindowWidth = size.Width;
_settingsService.DesktopWindowHeight = size.Height;
}
else if (ViewModel.IsDockMode)
{
}
else
{
_settingsService.StandardWindowLeft = rect.X;
_settingsService.StandardWindowTop = rect.Y;
_settingsService.StandardWindowWidth = size.Width;
_settingsService.StandardWindowHeight = size.Height;
}
}
else
{
_settingsService.StandardWindowLeft = rect.X;
_settingsService.StandardWindowTop = rect.Y;
_settingsService.StandardWindowWidth = size.Width;
_settingsService.StandardWindowHeight = size.Height;
}
}
}
@@ -164,7 +166,7 @@ namespace BetterLyrics.WinUI3.Views
private void SettingsMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
WindowHelper.OpenOrShowWindow<SettingsWindow>();
WindowHelper.OpenWindow<SettingsWindow>();
}
private void UpdateTitleBarWindowButtonsVisibility()
@@ -259,7 +261,8 @@ namespace BetterLyrics.WinUI3.Views
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
WindowHelper.ExitAllWindows();
DockModeHelper.Disable(this);
App.Current.Exit();
}
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
@@ -310,23 +313,22 @@ namespace BetterLyrics.WinUI3.Views
private void RootGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
ViewModel.IsMouseWithinWindow = true;
e.Handled = true;
}
private void RootGrid_PointerExited(object sender, PointerRoutedEventArgs e)
{
ViewModel.IsMouseWithinWindow = false;
e.Handled = true;
}
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Height < 100)
{
TopCommandGrid.Margin = new Thickness(0);
}
else
{
TopCommandGrid.Margin = new Thickness(12);
}
}
private void MusicGalleryButton_Click(object sender, RoutedEventArgs e)
{
WindowHelper.OpenWindow<MusicGalleryWindow>();
}
}
}

View File

@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="BetterLyrics.WinUI3.Views.MusicGalleryPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:labs="using:CommunityToolkit.Labs.WinUI"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:ui="using:CommunityToolkit.WinUI"
Loaded="Page_Loaded"
mc:Ignorable="d">
<Grid>
<Grid Padding="0,12,0,0">
<controls:Segmented
x:Name="Segmented"
Margin="36,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SelectedIndex="0"
SelectionMode="Single">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=Segmented, Path=SelectedIndex, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Tag" Value="Song" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=Segmented, Path=SelectedIndex, Mode=OneWay}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Tag" Value="Album" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=Segmented, Path=SelectedIndex, Mode=OneWay}"
ComparisonCondition="Equal"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Tag" Value="Artist" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<controls:SegmentedItem Content="歌曲" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC4F;}" />
<controls:SegmentedItem Content="专辑" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE93C;}" />
<controls:SegmentedItem Content="艺术家" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEFA9;}" />
</controls:Segmented>
<controls:OpacityMaskView Margin="0,36,0,0" HorizontalContentAlignment="Stretch">
<controls:OpacityMaskView.OpacityMask>
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.05" Color="White" />
<GradientStop Offset="0.95" Color="White" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</controls:OpacityMaskView.OpacityMask>
<Grid>
<ScrollViewer>
<controls:SwitchPresenter Padding="36,6" Value="{Binding ElementName=Segmented, Path=Tag, Mode=OneWay}">
<controls:SwitchPresenter.ContentTransitions>
<TransitionCollection>
<PopupThemeTransition />
</TransitionCollection>
</controls:SwitchPresenter.ContentTransitions>
<controls:Case Value="Song">
<ListView ItemsSource="{x:Bind ViewModel.Tracks, Mode=OneWay}" SelectionChanged="SongListView_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Padding="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<!-- 歌曲名 -->
<ColumnDefinition Width="1.5*" />
<!-- 歌手名 -->
<ColumnDefinition Width="1.5*" />
<!-- 专辑名 -->
<ColumnDefinition Width="1*" />
<!-- 年份 -->
<ColumnDefinition Width="1.2*" />
<!-- 流派 -->
<ColumnDefinition Width="1*" />
<!-- 歌曲时长 -->
</Grid.ColumnDefinitions>
<!-- 歌曲名 -->
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Title}"
TextWrapping="Wrap" />
<!-- 歌手名 -->
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Artist}"
TextWrapping="Wrap" />
<!-- 专辑名 -->
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Album}"
TextWrapping="Wrap" />
<!-- 年份 -->
<TextBlock
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Year}"
TextWrapping="Wrap" />
<!-- 流派 -->
<TextBlock
Grid.Column="4"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Genre}"
TextWrapping="Wrap" />
<!-- 歌曲时长 -->
<TextBlock
Grid.Column="5"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
TextAlignment="Right"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</controls:Case>
<controls:Case Value="Album">
<ListView />
</controls:Case>
<controls:Case Value="Artist">
<ListView />
</controls:Case>
</controls:SwitchPresenter>
</ScrollViewer>
</Grid>
</controls:OpacityMaskView>
</Grid>
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}" Visibility="{x:Bind ViewModel.IsDataLoading, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="12" />
<RowDefinition Height="*" />
<RowDefinition Height="12" />
<RowDefinition Height="*" />
<RowDefinition Height="12" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<labs:Shimmer Grid.Row="0" CornerRadius="12" />
<labs:Shimmer Grid.Row="2" CornerRadius="12" />
<labs:Shimmer Grid.Row="4" CornerRadius="12" />
<labs:Shimmer Grid.Row="6" CornerRadius="12" />
</Grid>
<ProgressRing IsActive="{x:Bind ViewModel.IsDataLoading, Mode=OneWay}" />
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,45 @@
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MusicGalleryPage : Page
{
public MusicGalleryViewModel ViewModel => (MusicGalleryViewModel)DataContext;
public MusicGalleryPage()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<MusicGalleryViewModel>();
}
private void SongListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel.PlaySongAt((sender as ListView)?.SelectedIndex);
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
ViewModel.RefreshSongs();
}
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<Window
x:Class="BetterLyrics.WinUI3.Views.MusicGalleryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MusicGalleryWindow"
mc:Ignorable="d">
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<local:MusicGalleryPage />
</Window>

View File

@@ -0,0 +1,36 @@
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using WinUIEx;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Views
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MusicGalleryWindow : Window
{
public MusicGalleryWindow()
{
InitializeComponent();
Title = App.ResourceLoader!.GetString("MusicGalleryPageTitle");
AppWindow.TitleBar.PreferredTheme = TitleBarTheme.UseDefaultAppMode;
this.SetIcon(@"Assets/Logo.ico");
}
}
}

View File

@@ -391,7 +391,7 @@
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8B7;}"
IsExpanded="True"
ItemsSource="{x:Bind ViewModel.LocalLyricsFolders, Mode=OneWay}">
ItemsSource="{x:Bind ViewModel.LocalMediaFolders, Mode=OneWay}">
<controls:SettingsExpander.ItemTemplate>
<DataTemplate>
<controls:SettingsCard>
@@ -426,13 +426,13 @@
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LocalLyricsFolders.Count, Mode=OneWay}"
Binding="{x:Bind ViewModel.LocalMediaFolders.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LocalLyricsFolders.Count, Mode=OneWay}"
Binding="{x:Bind ViewModel.LocalMediaFolders.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />

View File

@@ -26,7 +26,7 @@ namespace BetterLyrics.WinUI3.Views
{
if (sender is ToggleSwitch toggleSwitch)
{
if (toggleSwitch.DataContext is LocalLyricsFolder localLyricsFolder)
if (toggleSwitch.DataContext is LocalMediaFolder localLyricsFolder)
{
ViewModel.ToggleLocalLyricsFolder(localLyricsFolder);
}
@@ -65,7 +65,7 @@ namespace BetterLyrics.WinUI3.Views
Microsoft.UI.Xaml.RoutedEventArgs e
)
{
ViewModel.RemoveFolderAsync((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
ViewModel.RemoveFolderAsync((LocalMediaFolder)(sender as HyperlinkButton)!.Tag);
}
private void MediaSourceProviderToggleSwitch_Toggled(object sender, RoutedEventArgs e)

View File

@@ -12,6 +12,8 @@
<MicaBackdrop />
</Window.SystemBackdrop>
<local:SettingsPage />
<Grid x:Name="RootGrid">
<local:SettingsPage />
</Grid>
</Window>

View File

@@ -1,6 +1,6 @@
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using H.NotifyIcon;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using WinUIEx;
@@ -8,26 +8,14 @@ namespace BetterLyrics.WinUI3.Views
{
public sealed partial class SettingsWindow : Window
{
public SettingsWindowViewModel ViewModel { get; set; } = Ioc.Default.GetRequiredService<SettingsWindowViewModel>();
public SettingsWindow()
{
InitializeComponent();
Title = App.ResourceLoader!.GetString("SettingsPageTitle");
ExtendsContentIntoTitleBar = true;
AppWindow.Closing += AppWindow_Closing;
}
public SettingsWindowViewModel ViewModel { get; set; } =
Ioc.Default.GetRequiredService<SettingsWindowViewModel>();
private void AppWindow_Closing(
Microsoft.UI.Windowing.AppWindow sender,
Microsoft.UI.Windowing.AppWindowClosingEventArgs args
)
{
args.Cancel = true; // Prevent the window from closing
this.Hide(true);
AppWindow.TitleBar.PreferredTheme = TitleBarTheme.UseDefaultAppMode;
this.SetIcon(@"Assets/Logo.ico");
}
}
}