mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 19:24:55 +08:00
Compare commits
79 Commits
v1.2.232.0
...
77a9bb0a1b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77a9bb0a1b | ||
|
|
c07389acfb | ||
|
|
042229ae74 | ||
|
|
caaf93cf27 | ||
|
|
92e4b9468c | ||
|
|
6f60952d09 | ||
|
|
efc175668e | ||
|
|
3bf0fbef5f | ||
|
|
96b7835e8f | ||
|
|
a0b6511a53 | ||
|
|
3947050d6f | ||
|
|
707d85bc75 | ||
|
|
78bafb8508 | ||
|
|
b4d24c5570 | ||
|
|
83c9f9806d | ||
|
|
adde74afb0 | ||
|
|
67b4d4e409 | ||
|
|
8d7fbe63c5 | ||
|
|
5037b92913 | ||
|
|
c1ee7a6779 | ||
|
|
7ddfd1118b | ||
|
|
97f20decf2 | ||
|
|
81eb4e1c96 | ||
|
|
00ee4a051c | ||
|
|
a13bb6e8e4 | ||
|
|
0b436c1ea9 | ||
|
|
5d332fdfc6 | ||
|
|
572d2cd8ba | ||
|
|
1e5a95c55e | ||
|
|
18ce6d3a57 | ||
|
|
427aed6857 | ||
|
|
ebfa484a2e | ||
|
|
3ef9d81bea | ||
|
|
e999d07834 | ||
|
|
838b8de94f | ||
|
|
b3059dbeb1 | ||
|
|
6fea88a6a1 | ||
|
|
abca9ae5fb | ||
|
|
a062897e1a | ||
|
|
8b4748df1b | ||
|
|
1df5ea6bab | ||
|
|
c576635af2 | ||
|
|
c8590202ec | ||
|
|
2dc8b1283f | ||
|
|
c482edea0f | ||
|
|
315722252c | ||
|
|
32ba453264 | ||
|
|
d4902329bb | ||
|
|
83aee8948b | ||
|
|
1f9fab3228 | ||
|
|
7a3a659dfc | ||
|
|
a14afd3eb5 | ||
|
|
c2af7f3186 | ||
|
|
cd026dd2bf | ||
|
|
4bc1a9975d | ||
|
|
07eecf0930 | ||
|
|
35fba5abb0 | ||
|
|
03ef231a3f | ||
|
|
f41879f4e5 | ||
|
|
bda7510ed6 | ||
|
|
5ec8c7c61f | ||
|
|
7e6bd9dade | ||
|
|
56244cb793 | ||
|
|
cb5f70ab55 | ||
|
|
3cc018bb1f | ||
|
|
c517d2b008 | ||
|
|
e79f2a0223 | ||
|
|
39122b9147 | ||
|
|
accbdc1806 | ||
|
|
de014d1ad7 | ||
|
|
cc2ce5f8cf | ||
|
|
2a2d80436e | ||
|
|
ce3f79f35c | ||
|
|
12e6000cb3 | ||
|
|
c1dc684411 | ||
|
|
69ea2cb495 | ||
|
|
e2ee03c4be | ||
|
|
c6fe33d6ae | ||
|
|
7744e145fa |
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.2.232.0" />
|
||||
Version="1.2.249.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
x:Class="BetterLyrics.WinUI3.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converter="using:BetterLyrics.WinUI3.Converter"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:globalization="using:Windows.Globalization"
|
||||
xmlns:local="using:BetterLyrics.WinUI3"
|
||||
xmlns:media="using:CommunityToolkit.WinUI.Media">
|
||||
@@ -13,8 +11,14 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- Merged dictionaries here -->
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///DevWinUI.Controls/Themes/Generic.xaml" />
|
||||
|
||||
<ResourceDictionary Source="/Styles/Converters.xaml" />
|
||||
<ResourceDictionary Source="/Styles/InteractiveListViewHeaderStyle.xaml" />
|
||||
<ResourceDictionary Source="/Styles/GhostSliderStyle.xaml" />
|
||||
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Theme -->
|
||||
@@ -42,46 +46,6 @@
|
||||
<ExponentialEase x:Key="EaseOut" EasingMode="EaseOut" />
|
||||
<ExponentialEase x:Key="EaseIn" EasingMode="EaseIn" />
|
||||
|
||||
<!-- Converter -->
|
||||
<converter:EnumToIntConverter x:Key="EnumToIntConverter" />
|
||||
<converter:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
||||
<converter:MatchedLocalFilesPathToVisibilityConverter x:Key="MatchedLocalFilesPathToVisibilityConverter" />
|
||||
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
|
||||
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
|
||||
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
|
||||
<converter:TransliterationSearchProviderToDisplayNameConverter x:Key="TransliterationSearchProviderToDisplayNameConverter" />
|
||||
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
|
||||
<converter:MillisecondsToFormattedTimeConverter x:Key="MillisecondsToFormattedTimeConverter" />
|
||||
<converter:FPSToTimeSpanConverter x:Key="FPSToTimeSpanConverter" />
|
||||
<converter:ShortcutToStringConverter x:Key="ShortcutToStringConverter" />
|
||||
<converter:BoolNegationToVisibilityConverter x:Key="BoolNegationToVisibilityConverter" />
|
||||
<converter:BoolToOpacityConverter x:Key="BoolToOpacityConverter" />
|
||||
<converter:BoolToPartialOpacityConverter x:Key="BoolToPartialOpacityConverter" />
|
||||
<converter:BoolNegationToOpacityConverter x:Key="BoolNegationToOpacityConverter" />
|
||||
<converter:RectToMarginConverter x:Key="RectToMarginConverter" />
|
||||
<converter:LanguageCodeToDisplayedNameConverter x:Key="LanguageCodeToDisplayedNameConverter" />
|
||||
<converter:ByteArrayToImageConverter x:Key="ByteArrayToImageConverter" />
|
||||
<converter:DisplayLanguageCodeToIndexConverter x:Key="DisplayLanguageCodeToIndexConverter" />
|
||||
<converter:PathToParentFolderConverter x:Key="PathToParentFolderConverter" />
|
||||
<converter:IntToBoolConverter x:Key="IntToBoolConverter" />
|
||||
<converter:IndexToDisplayConverter x:Key="IndexToDisplayConverter" />
|
||||
<converter:IntToDoubleConverter x:Key="IntToDoubleConverter" />
|
||||
<converter:MillisecondsToSecondsConverter x:Key="MillisecondsToSecondsConverter" />
|
||||
<converter:PictureInfosToImageSourceConverter x:Key="PictureInfosToImageSourceConverter" />
|
||||
<converter:LyricsFontWeightToFontWeightConverter x:Key="LyricsFontWeightToFontWeightConverter" />
|
||||
<converter:TextAlignmentTypeToHorizontalAlignmentConverter x:Key="TextAlignmentTypeToHorizontalAlignmentConverter" />
|
||||
<converter:LyricsLayoutOrientationToOrientationConverter x:Key="LyricsLayoutOrientationToOrientationConverter" />
|
||||
<converter:LyricsLayoutOrientationNegationToOrientationConverter x:Key="LyricsLayoutOrientationNegationToOrientationConverter" />
|
||||
<converter:FileSourceTypeToIconConverter x:Key="FileSourceTypeToIconConverter" />
|
||||
<converter:PathToImageConverter x:Key="PathToImageConverter" />
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
<converters:CollectionVisibilityConverter x:Key="CollectionVisibilityConverter" />
|
||||
|
||||
<x:Double x:Key="SettingsCardSpacing">4</x:Double>
|
||||
|
||||
<!-- Style -->
|
||||
@@ -109,7 +73,7 @@
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="TitleBarToggleButtonStyle"
|
||||
BasedOn="{StaticResource ToggleButtonRevealStyle}"
|
||||
BasedOn="{StaticResource DefaultToggleButtonStyle}"
|
||||
TargetType="ToggleButton">
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
@@ -117,7 +81,10 @@
|
||||
<Setter Property="Padding" Value="14,6,14,9" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Style
|
||||
x:Key="GhostToggleButtonStyle"
|
||||
BasedOn="{StaticResource DefaultToggleButtonStyle}"
|
||||
TargetType="ToggleButton">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
@@ -131,190 +98,6 @@
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GhostSliderStyle" TargetType="Slider">
|
||||
<Setter Property="Background" Value="{ThemeResource ControlStrokeColorOnAccentDefaultBrush}" />
|
||||
<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="ListViewStretchedItemContainerStyle"
|
||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||
@@ -358,10 +141,6 @@
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
|
||||
</Style>
|
||||
|
||||
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
|
||||
<!-- Dimensions -->
|
||||
|
||||
<!-- Fonts -->
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models.Db;
|
||||
using BetterLyrics.WinUI3.Models.DbContext;
|
||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||
using BetterLyrics.WinUI3.Services.DiscordService;
|
||||
using BetterLyrics.WinUI3.Services.FileSystemService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||
using BetterLyrics.WinUI3.Services.LyricsCacheService;
|
||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.PlayHistoryService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.SMTCService;
|
||||
using BetterLyrics.WinUI3.Services.SongSearchMapService;
|
||||
using BetterLyrics.WinUI3.Services.TranslationService;
|
||||
using BetterLyrics.WinUI3.Services.TransliterationService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching; // 关键:用于线程调度
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle; // 关键:App生命周期管理
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -126,13 +126,25 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
// 初始化数据库
|
||||
await EnsureDatabasesAsync();
|
||||
await InitDatabasesAsync();
|
||||
|
||||
var settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
// Migrate MappedSongSearchQueries
|
||||
var songSearchMapService = Ioc.Default.GetRequiredService<ISongSearchMapService>();
|
||||
var obsoleteSongSearchMap = settingsService.AppSettings.MappedSongSearchQueries;
|
||||
if (obsoleteSongSearchMap.Count > 0)
|
||||
{
|
||||
foreach (var item in obsoleteSongSearchMap)
|
||||
{
|
||||
await songSearchMapService.SaveMappingAsync(item);
|
||||
}
|
||||
obsoleteSongSearchMap.Clear();
|
||||
}
|
||||
|
||||
// Start scan tasks in background
|
||||
var fileSystemService = Ioc.Default.GetRequiredService<IFileSystemService>();
|
||||
|
||||
// 开始后台扫描任务
|
||||
foreach (var item in settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
if (item.LastSyncTime == null)
|
||||
@@ -142,10 +154,10 @@ namespace BetterLyrics.WinUI3
|
||||
}
|
||||
fileSystemService.StartAllFolderTimers();
|
||||
|
||||
// 初始化托盘
|
||||
// Init system tray
|
||||
m_window = WindowHook.OpenOrShowWindow<SystemTrayWindow>();
|
||||
|
||||
// 根据设置打开歌词窗口
|
||||
// Open lyrics window if set
|
||||
if (settingsService.AppSettings.GeneralSettings.AutoStartLyricsWindow)
|
||||
{
|
||||
var defaultStatus = settingsService.AppSettings.WindowBoundsRecords.Where(x => x.IsDefault);
|
||||
@@ -162,97 +174,39 @@ namespace BetterLyrics.WinUI3
|
||||
}
|
||||
}
|
||||
|
||||
// 根据设置自动打开主界面
|
||||
// Open music gallery if set
|
||||
if (settingsService.AppSettings.MusicGallerySettings.AutoOpen)
|
||||
{
|
||||
WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureDatabasesAsync()
|
||||
private async Task InitDatabasesAsync()
|
||||
{
|
||||
// Init databases
|
||||
var playHistoryFactory = Ioc.Default.GetRequiredService<IDbContextFactory<PlayHistoryDbContext>>();
|
||||
var fileCacheFactory = Ioc.Default.GetRequiredService<IDbContextFactory<FilesIndexDbContext>>();
|
||||
var songSearchMapFactory = Ioc.Default.GetRequiredService<IDbContextFactory<SongSearchMapDbContext>>();
|
||||
var filesIndexFactory = Ioc.Default.GetRequiredService<IDbContextFactory<FilesIndexDbContext>>();
|
||||
var lyricsCacheFactory = Ioc.Default.GetRequiredService<IDbContextFactory<LyricsCacheDbContext>>();
|
||||
|
||||
await SafeInitDatabaseAsync(
|
||||
"PlayHistory",
|
||||
PathHelper.PlayHistoryPath,
|
||||
async () =>
|
||||
{
|
||||
using var db = await playHistoryFactory.CreateDbContextAsync();
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
},
|
||||
isCritical: true
|
||||
);
|
||||
|
||||
await SafeInitDatabaseAsync(
|
||||
"FileCache",
|
||||
PathHelper.FilesIndexPath,
|
||||
async () =>
|
||||
{
|
||||
using var db = await fileCacheFactory.CreateDbContextAsync();
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
},
|
||||
isCritical: false
|
||||
);
|
||||
}
|
||||
|
||||
private async Task SafeInitDatabaseAsync(string dbName, string dbPath, Func<Task> initAction, bool isCritical)
|
||||
{
|
||||
try
|
||||
using (var playHistoryDb = await playHistoryFactory.CreateDbContextAsync())
|
||||
{
|
||||
await initAction();
|
||||
await playHistoryDb.Database.EnsureCreatedAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
using (var songSearchMapDb = await songSearchMapFactory.CreateDbContextAsync())
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[DB Error] {dbName} init failed: {ex.Message}");
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(dbPath))
|
||||
{
|
||||
// 尝试清理连接池
|
||||
SqliteConnection.ClearAllPools();
|
||||
|
||||
if (isCritical)
|
||||
{
|
||||
var backupPath = dbPath + ".bak_" + DateTime.Now.ToString("yyyyMMddHHmmss");
|
||||
File.Move(dbPath, backupPath, true);
|
||||
await ShowErrorDialogAsync("Database Recovery", $"Database {dbName} is damaged, the old database has been backed up to {backupPath}, and the program will create a new database.");
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(dbPath);
|
||||
}
|
||||
}
|
||||
await initAction();
|
||||
System.Diagnostics.Debug.WriteLine($"[DB Info] {dbName} recovered successfully.");
|
||||
}
|
||||
catch (Exception fatalEx)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[] : {fatalEx.Message}");
|
||||
await ShowErrorDialogAsync("Fatal Error", $"{dbName} recovery failed, please delete the file at {dbPath} and try again by restarting the program. ({fatalEx.Message})");
|
||||
}
|
||||
await songSearchMapDb.Database.EnsureCreatedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowErrorDialogAsync(string title, string content)
|
||||
{
|
||||
// 这里假设 m_window 已经存在。如果没有显示主窗口,这个弹窗可能无法显示。
|
||||
// 在 App 启动极早期的错误,可能需要退化为 Log 或者 System.Diagnostics.Process.Start 打开记事本报错
|
||||
if (m_window != null)
|
||||
using (var filesIndexDb = await filesIndexFactory.CreateDbContextAsync())
|
||||
{
|
||||
m_window.DispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog
|
||||
{
|
||||
Title = title,
|
||||
Content = content,
|
||||
CloseButtonText = "OK",
|
||||
XamlRoot = m_window.Content?.XamlRoot // 确保 Content 不为空
|
||||
};
|
||||
if (dialog.XamlRoot != null) await dialog.ShowAsync();
|
||||
});
|
||||
await filesIndexDb.Database.EnsureCreatedAsync();
|
||||
}
|
||||
|
||||
using (var lyricsCacheDb = await lyricsCacheFactory.CreateDbContextAsync())
|
||||
{
|
||||
await lyricsCacheDb.Database.EnsureCreatedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,6 +214,7 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
|
||||
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", Serilog.Events.LogEventLevel.Error)
|
||||
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
@@ -268,6 +223,8 @@ namespace BetterLyrics.WinUI3
|
||||
// 数据库工厂
|
||||
.AddDbContextFactory<PlayHistoryDbContext>(options => options.UseSqlite($"Data Source={PathHelper.PlayHistoryPath}"))
|
||||
.AddDbContextFactory<FilesIndexDbContext>(options => options.UseSqlite($"Data Source={PathHelper.FilesIndexPath}"))
|
||||
.AddDbContextFactory<LyricsCacheDbContext>(options => options.UseSqlite($"Data Source={PathHelper.LyricsCachePath}"))
|
||||
.AddDbContextFactory<SongSearchMapDbContext>(options => options.UseSqlite($"Data Source={PathHelper.SongSearchMapPath}"))
|
||||
|
||||
// 日志
|
||||
.AddLogging(loggingBuilder =>
|
||||
@@ -278,7 +235,8 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
// Services
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
|
||||
.AddSingleton<ISMTCService, SMTCService>()
|
||||
.AddSingleton<IGSMTCService, GSMTCService>()
|
||||
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
|
||||
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
|
||||
.AddSingleton<ITranslationService, TranslationService>()
|
||||
@@ -288,6 +246,8 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<ILocalizationService, LocalizationService>()
|
||||
.AddSingleton<IFileSystemService, FileSystemService>()
|
||||
.AddSingleton<IPlayHistoryService, PlayHistoryService>()
|
||||
.AddSingleton<ILyricsCacheService, LyricsCacheService>()
|
||||
.AddSingleton<ISongSearchMapService, SongSearchMapService>()
|
||||
|
||||
// ViewModels
|
||||
.AddSingleton<AppSettingsControlViewModel>()
|
||||
@@ -304,6 +264,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<AboutControlViewModel>()
|
||||
.AddSingleton<MusicGalleryWindowViewModel>()
|
||||
.AddSingleton<StatsDashboardControlViewModel>()
|
||||
.AddSingleton<PlayQueueViewModel>()
|
||||
|
||||
.AddTransient<NowPlayingWindowViewModel>()
|
||||
.AddTransient<NowPlayingPageViewModel>()
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 593 KiB |
@@ -42,13 +42,17 @@
|
||||
<None Remove="Controls\LyricsWindowSwitchControl.xaml" />
|
||||
<None Remove="Controls\MediaSettingsControl.xaml" />
|
||||
<None Remove="Controls\NowPlayingBar.xaml" />
|
||||
<None Remove="Controls\PatronControl.xaml" />
|
||||
<None Remove="Controls\PlaybackSettingsControl.xaml" />
|
||||
<None Remove="Controls\PlayQueue.xaml" />
|
||||
<None Remove="Controls\PropertyRow.xaml" />
|
||||
<None Remove="Controls\RemoteServerConfigControl.xaml" />
|
||||
<None Remove="Controls\ShortcutTextBox.xaml" />
|
||||
<None Remove="Controls\StatsDashboardControl.xaml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Controls\WindowSettingsControl.xaml" />
|
||||
<None Remove="Styles\GhostSliderStyle.xaml" />
|
||||
<None Remove="Styles\InteractiveListViewHeaderStyle.xaml" />
|
||||
<None Remove="Views\LyricsSearchWindow.xaml" />
|
||||
<None Remove="Views\LyricsWindowSwitchWindow.xaml" />
|
||||
<None Remove="Views\MusicGalleryPage.xaml" />
|
||||
@@ -86,8 +90,8 @@
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.4.1" />
|
||||
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
|
||||
<PackageReference Include="Interop.UIAutomationClient" Version="10.19041.0" />
|
||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.WinUI" Version="2.0.0-rc6.1" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.1" />
|
||||
@@ -243,6 +247,9 @@
|
||||
<Content Update="Assets\Question.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\RevolvingHearts.gif">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\SaltPlayerForWindows.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -259,6 +266,26 @@
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\PatronControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\GhostSliderStyle.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\Converters.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\PlayQueue.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\StatsDashboardControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -409,6 +436,11 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\InteractiveListViewHeaderStyle.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
|
||||
@@ -6,5 +6,7 @@ namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(250);
|
||||
public static readonly TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(350);
|
||||
public static readonly TimeSpan LongAnimationDuration = TimeSpan.FromMilliseconds(650);
|
||||
public static readonly TimeSpan WaitingDuration = TimeSpan.FromMilliseconds(300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,12 +57,12 @@
|
||||
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.BetterLyricsGitHub}" />
|
||||
<HyperlinkButton x:Uid="UserGuide" NavigateUri="{x:Bind const:Link.UserGuide}" />
|
||||
<HyperlinkButton x:Uid="PrivacyPolicy" NavigateUri="{x:Bind const:Link.PrivacyPolicy}" />
|
||||
<HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfService}" />
|
||||
</StackPanel>
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
</dev:SettingsCard>
|
||||
@@ -70,18 +70,18 @@
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageFeedback" />
|
||||
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="QQ 反馈交流群" NavigateUri="{x:Bind const:Link.QQGroup}" />
|
||||
<HyperlinkButton Content="Discord" NavigateUri="{x:Bind const:Link.Discord}" />
|
||||
<HyperlinkButton Content="Telegram" NavigateUri="{x:Bind const:Link.Telegram}" />
|
||||
</StackPanel>
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageDonation" />
|
||||
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="Buy Me a Coffee" NavigateUri="{x:Bind const:Link.BuyMeACoffee}" />
|
||||
<HyperlinkButton Content="PayPal" NavigateUri="{x:Bind const:Link.PayPal}" />
|
||||
<HyperlinkButton
|
||||
@@ -117,7 +117,7 @@
|
||||
</HyperlinkButton.ContextFlyout>
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Content="爱发电" NavigateUri="{x:Bind const:Link.Afdian}" />
|
||||
</StackPanel>
|
||||
</dev:WrapPanel>
|
||||
<Grid ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -136,15 +136,6 @@
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageThanksList">
|
||||
<Button
|
||||
Click="Patron_Click"
|
||||
Content="{ui:FontIcon FontSize=16,
|
||||
FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
</dev:SettingsExpander.Items>
|
||||
<dev:SettingsExpander.ItemsFooter>
|
||||
<InfoBar
|
||||
@@ -157,6 +148,108 @@
|
||||
</dev:SettingsExpander.ItemsFooter>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsExpander x:Uid="SettingsPageThanksList">
|
||||
<dev:SettingsExpander.HeaderIcon>
|
||||
<ImageIcon Source="ms-appx:///Assets/RevolvingHearts.gif" />
|
||||
</dev:SettingsExpander.HeaderIcon>
|
||||
<dev:SettingsExpander.Items>
|
||||
|
||||
<!-- 贡献者 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<RichTextBlock>
|
||||
<Paragraph>
|
||||
<Run x:Uid="SetingsPageContributors" />
|
||||
<Run Text="-" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Code" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="jayfunc" NavigateUri="https://github.com/jayfunc" />
|
||||
<HyperlinkButton Content="Raspberry-Monster" NavigateUri="https://github.com/Raspberry-Monster" />
|
||||
<HyperlinkButton Content="ZHider" NavigateUri="https://github.com/ZHider" />
|
||||
<HyperlinkButton Content="kusutori" NavigateUri="https://github.com/kusutori" />
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 贡献者 (Translator) -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<RichTextBlock>
|
||||
<Paragraph>
|
||||
<Run x:Uid="SetingsPageContributors" />
|
||||
<Run Text="-" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Translator" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="borcolasky" NavigateUri="https://crowdin.com/profile/borcolasky" />
|
||||
<HyperlinkButton Content="SuHeAndZl" NavigateUri="https://crowdin.com/profile/SuHeAndZl" />
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 赞助 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SettingsPagePatrons" />
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<uc:PatronControl Date="Jan 8, 2026" PatronName="Eureka-K_K" />
|
||||
<uc:PatronControl Date="Jan 3, 2026" PatronName="**轩" />
|
||||
<uc:PatronControl Date="Dec 13, 2025" PatronName="<Anonymous>" />
|
||||
<uc:PatronControl Date="Dec 3, 2025" PatronName="YE" />
|
||||
<uc:PatronControl Date="Dec 2, 2025" PatronName="<Anonymous>" />
|
||||
<uc:PatronControl Date="Nov 23, 2025" PatronName="**玄" />
|
||||
<uc:PatronControl Date="Nov 21, 2025" PatronName="**智" />
|
||||
<uc:PatronControl Date="Nov 17, 2025" PatronName="SuHeAndZl" />
|
||||
<uc:PatronControl Date="Nov 2, 2025" PatronName="借过" />
|
||||
<uc:PatronControl Date="Aug 28, 2025" PatronName="**华" />
|
||||
<TextBlock
|
||||
x:Uid="SettingsPageUserWhoPurchased"
|
||||
Margin="12,8"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 特别鸣谢 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageSpecialThanks" />
|
||||
<TextBlock
|
||||
x:Uid="SettingsPageYouNowUsing"
|
||||
Margin="0,8"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 代码参考 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageDeps" />
|
||||
<HyperlinkButton Margin="-12,0,0,0" NavigateUri="https://github.com/jayfunc/BetterLyrics/network/dependencies">
|
||||
<TextBlock x:Uid="SetingsPageDeps" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- UI/UX 参考 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageUIUXRef" />
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="refined-now-playing-netease" NavigateUri="https://github.com/solstice23/refined-now-playing-netease" />
|
||||
<HyperlinkButton Content="Lyricify" NavigateUri="https://github.com/WXRIW/Lyricify-App" />
|
||||
<HyperlinkButton Content="椒盐音乐 Salt Player" NavigateUri="https://moriafly.com/program/salt-player" />
|
||||
<HyperlinkButton Content="MyToolBar" NavigateUri="https://github.com/TwilightLemon/MyToolBar" />
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageMockMusicPlaying">
|
||||
<HyperlinkButton x:Uid="SettingsPagePlayingMockMusicButton" NavigateUri="https://soundcloud.com/carlyraejepsen/cut-to-the-feeling" />
|
||||
</dev:SettingsCard>
|
||||
@@ -190,12 +283,6 @@
|
||||
</dev:SettingsExpander.ItemsHeader>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageSettingsPlayHistory" Visibility="Collapsed">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button x:Uid="SettingsPageExportPlayHistoryButton" Command="{x:Bind ViewModel.ExportPlayHistoryCommand}" />
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageFixedTimeStep" Visibility="Collapsed">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.AdvancedSettings.IsFixedTimeStep, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
@@ -209,194 +296,28 @@
|
||||
Value="{x:Bind ViewModel.AppSettings.AdvancedSettings.FPS, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<RichTextBlock
|
||||
Margin="0,16,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
LineHeight="28">
|
||||
<Paragraph FontWeight="Bold">
|
||||
<Run Text="{x:Bind const:App.AppName}" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="An elegant and deeply customizable lyrics visualizer & versatile music player" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="Proudly built by" />
|
||||
<Hyperlink NavigateUri="{x:Bind const:Link.AuthorGitHub}">
|
||||
<Run Text="{x:Bind const:App.AppAuthor}" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<Grid
|
||||
x:Name="CreditsReel"
|
||||
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
|
||||
Opacity="0"
|
||||
SizeChanged="CreditsReel_SizeChanged"
|
||||
Tapped="CreditsReel_Tapped"
|
||||
Visibility="Collapsed">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<ScrollViewer
|
||||
x:Name="CreditsReelScrollViewer"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||
ScrollViewer.VerticalScrollMode="Disabled">
|
||||
<RichTextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
LineHeight="28"
|
||||
PointerEntered="RichTextBlock_PointerEntered"
|
||||
PointerExited="RichTextBlock_PointerExited">
|
||||
|
||||
<Paragraph x:Name="CreditsReelHeader" />
|
||||
|
||||
<!-- 贡献者 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SetingsPageContributors" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/jayfunc">
|
||||
<Run Text="jayfunc" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/Raspberry-Monster">
|
||||
<Run Text="Raspberry-Monster" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/ZHider">
|
||||
<Run Text="ZHider" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/kusutori">
|
||||
<Run Text="kusutori" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
|
||||
<!-- 赞助 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SettingsPagePatrons" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="YE" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Dec 3, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="**玄" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 23, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="**智" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 21, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="*鹤" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 17, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="借过" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 2, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="**华" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Aug 28, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run x:Uid="SettingsPageUserWhoPurchased" />
|
||||
</Paragraph>
|
||||
|
||||
<!-- 特别鸣谢 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SetingsPageSpecialThanks" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run x:Uid="SettingsPageYouNowUsing" />
|
||||
</Paragraph>
|
||||
|
||||
<!-- 代码参考 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SetingsPageDeps" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce">
|
||||
<Run Text="Get album artwork from ITunes (with Python3 or C#)" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://stackoverflow.com/a/32013610/11048731">
|
||||
<Run Text="FullyObservableCollection" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/Storyteller-Studios/Impressionist">
|
||||
<Run Text="Impressionist" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/Storyteller-Studios/ColorThief.WinUI3">
|
||||
<Run Text="ColorThief.WinUI3" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/Johnwikix/SpectrumVisualization">
|
||||
<Run Text="SpectrumVisualization" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://www.shadertoy.com/view/Mdt3Df">
|
||||
<Run Text="Snow (as shown in sweden)" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://www.shadertoy.com/view/lllSR2">
|
||||
<Run Text="w10" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/mo-jinran/Taskbar-Lyrics">
|
||||
<Run Text="Taskbar-Lyrics" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/jayfunc/BetterLyrics/network/dependencies">
|
||||
<Run Text="..." />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
|
||||
<!-- UI/UX 设计参考 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SetingsPageUIUXRef" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/solstice23/refined-now-playing-netease">
|
||||
<Run Text="refined-now-playing-netease" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/WXRIW/Lyricify-App">
|
||||
<Run Text="Lyricify" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://moriafly.com/program/salt-player">
|
||||
<Run Text="椒盐音乐 Salt Player" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/TwilightLemon/MyToolBar">
|
||||
<Run Text="MyToolBar" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="">
|
||||
<Run Text="" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run Text="{x:Bind const:App.AppName}" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="Proudly built by" />
|
||||
<Hyperlink NavigateUri="{x:Bind const:Link.AuthorGitHub}">
|
||||
<Run Text="{x:Bind const:App.AppAuthor}" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph x:Name="CreditsReelFooter" />
|
||||
|
||||
</RichTextBlock>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
@@ -11,7 +9,6 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
public sealed partial class AboutControl : UserControl
|
||||
{
|
||||
private bool _isCreditsScrolling = false;
|
||||
public AboutControlViewModel ViewModel => (AboutControlViewModel)DataContext;
|
||||
|
||||
public AboutControl()
|
||||
@@ -20,47 +17,6 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
DataContext = Ioc.Default.GetRequiredService<AboutControlViewModel>();
|
||||
}
|
||||
|
||||
private async void Patron_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
CompositionTarget.Rendering += CompositionTarget_Rendering;
|
||||
CreditsReel.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
|
||||
CreditsReel.Opacity = 1;
|
||||
_isCreditsScrolling = true;
|
||||
}
|
||||
|
||||
private void CompositionTarget_Rendering(object? sender, object e)
|
||||
{
|
||||
if (_isCreditsScrolling)
|
||||
{
|
||||
CreditsReelScrollViewer.ChangeView(null, CreditsReelScrollViewer.VerticalOffset + 0.5, null);
|
||||
}
|
||||
}
|
||||
|
||||
private async void CreditsReel_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
|
||||
{
|
||||
CreditsReel.Opacity = 0;
|
||||
await Task.Delay(Constants.Time.AnimationDuration);
|
||||
CreditsReel.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
|
||||
CompositionTarget.Rendering -= CompositionTarget_Rendering;
|
||||
CreditsReelScrollViewer.ChangeView(null, 0, null);
|
||||
}
|
||||
|
||||
private void CreditsReel_SizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e)
|
||||
{
|
||||
CreditsReelHeader.LineHeight = e.NewSize.Height;
|
||||
CreditsReelFooter.LineHeight = e.NewSize.Height / 2;
|
||||
}
|
||||
|
||||
private void RichTextBlock_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
_isCreditsScrolling = false;
|
||||
}
|
||||
|
||||
private void RichTextBlock_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
_isCreditsScrolling = true;
|
||||
}
|
||||
|
||||
private void WeChat_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
WeChatFlyout.ShowAt(WeChatButton);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
@@ -5,10 +5,10 @@ using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Logic;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Lyrics;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Renderer;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -31,7 +31,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
public sealed partial class LyricsCanvas : UserControl,
|
||||
IRecipient<PropertyChangedMessage<TimeSpan>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsData?>>,
|
||||
IRecipient<PropertyChangedMessage<SongInfo?>>,
|
||||
IRecipient<PropertyChangedMessage<SongInfo>>,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<double>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
@@ -41,7 +41,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
IRecipient<PropertyChangedMessage<IRandomAccessStream?>>
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
private readonly IGSMTCService _gsmtcService = Ioc.Default.GetRequiredService<IGSMTCService>();
|
||||
|
||||
private readonly LyricsRenderer _lyricsRenderer = new();
|
||||
private readonly FluidBackgroundRenderer _fluidRenderer = new();
|
||||
@@ -58,48 +58,46 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private readonly ValueTransition<Color> _immersiveBgColorTransition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
defaultTotalDuration: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<double> _immersiveBgOpacityTransition = new(
|
||||
initialValue: 1f,
|
||||
durationSeconds: 0.3f
|
||||
defaultTotalDuration: 0.3f
|
||||
);
|
||||
private readonly ValueTransition<Color> _accentColor1Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
defaultTotalDuration: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<Color> _accentColor2Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
defaultTotalDuration: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<Color> _accentColor3Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
defaultTotalDuration: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<Color> _accentColor4Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
defaultTotalDuration: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<double> _canvasYScrollTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
defaultTotalDuration: 0.3f,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
private readonly ValueTransition<double> _mouseYScrollTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
defaultTotalDuration: 0.3f,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
|
||||
private TimeSpan _songPositionWithOffset;
|
||||
private TimeSpan _songPosition; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1>
|
||||
private TimeSpan _totalPlayedTime; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ػ<EFBFBD><D8BB>ظ<EFBFBD><D8B8><EFBFBD><EFBFBD>ŵ<EFBFBD>ʱ<EFBFBD>䣩
|
||||
private bool _isLastFMTracked = false;
|
||||
|
||||
private double _renderLyricsStartX = 0;
|
||||
private double _renderLyricsStartY = 0;
|
||||
@@ -121,7 +119,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
private bool _isLayoutChanged = true;
|
||||
private bool _isMouseScrollingChanged = false;
|
||||
|
||||
private int _playingLineIndex;
|
||||
private int _primaryPlayingLineIndex;
|
||||
private (int Start, int End) _visibleRange;
|
||||
private double _canvasTargetScrollOffset;
|
||||
|
||||
@@ -294,7 +292,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
}
|
||||
else if (e.Property == MouseScrollOffsetProperty)
|
||||
{
|
||||
canvas._mouseYScrollTransition.StartTransition(Convert.ToDouble(e.NewValue));
|
||||
canvas._mouseYScrollTransition.Start(Convert.ToDouble(e.NewValue));
|
||||
}
|
||||
else if (e.Property == MousePositionProperty)
|
||||
{
|
||||
@@ -320,11 +318,11 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
else if (e.Property == AlbumArtThemeColorsProperty)
|
||||
{
|
||||
var albumArtThemeColors = (AlbumArtThemeColors)e.NewValue;
|
||||
canvas._immersiveBgColorTransition.StartTransition(albumArtThemeColors.EnvColor);
|
||||
canvas._accentColor1Transition.StartTransition(albumArtThemeColors.AccentColor1);
|
||||
canvas._accentColor2Transition.StartTransition(albumArtThemeColors.AccentColor2);
|
||||
canvas._accentColor3Transition.StartTransition(albumArtThemeColors.AccentColor3);
|
||||
canvas._accentColor4Transition.StartTransition(albumArtThemeColors.AccentColor4);
|
||||
canvas._immersiveBgColorTransition.Start(albumArtThemeColors.EnvColor);
|
||||
canvas._accentColor1Transition.Start(albumArtThemeColors.AccentColor1);
|
||||
canvas._accentColor2Transition.Start(albumArtThemeColors.AccentColor2);
|
||||
canvas._accentColor3Transition.Start(albumArtThemeColors.AccentColor3);
|
||||
canvas._accentColor4Transition.Start(albumArtThemeColors.AccentColor4);
|
||||
|
||||
canvas._albumArtThemeColors = albumArtThemeColors;
|
||||
canvas._isLayoutChanged = true;
|
||||
@@ -345,8 +343,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
var lyricsStyle = _lyricsWindowStatus.LyricsStyleSettings;
|
||||
var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
|
||||
|
||||
double songDuration = _mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0;
|
||||
bool isForceWordByWord = _settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect;
|
||||
double songDuration = _gsmtcService.CurrentSongInfo.DurationMs;
|
||||
|
||||
Color overlayColor;
|
||||
double finalOpacity;
|
||||
@@ -384,7 +381,6 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
control: sender,
|
||||
ds: args.DrawingSession,
|
||||
lines: _renderLyricsLines,
|
||||
playingLineIndex: _playingLineIndex,
|
||||
mouseHoverLineIndex: _mouseHoverLineIndex,
|
||||
isMousePressing: _isMousePressing,
|
||||
startVisibleIndex: _visibleRange.Start,
|
||||
@@ -400,6 +396,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
strokeColor: _albumArtThemeColors.StrokeFontColor,
|
||||
bgColor: _albumArtThemeColors.BgFontColor,
|
||||
fgColor: _albumArtThemeColors.FgFontColor,
|
||||
currentProgressMs: _songPositionWithOffset.TotalMilliseconds,
|
||||
getPlaybackState: (lineIndex) =>
|
||||
{
|
||||
if (_renderLyricsLines == null) return new LinePlaybackState();
|
||||
@@ -412,9 +409,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
return _synchronizer.GetLinePlayingProgress(
|
||||
_songPositionWithOffset.TotalMilliseconds,
|
||||
line,
|
||||
nextLine,
|
||||
songDuration,
|
||||
isForceWordByWord
|
||||
lyricsEffect.WordByWordEffectMode
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -435,19 +430,19 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
//args.DrawingSession.DrawText(
|
||||
// $"Lyrics render start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" +
|
||||
// $"Lyrics render size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" +
|
||||
// $"Lyrics actual height: {LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines)}\n" +
|
||||
// $"Playing line (idx): {_playingLineIndex}\n" +
|
||||
// $"Mouse hovering line (idx): {_mouseHoverLineIndex}\n" +
|
||||
// $"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" +
|
||||
// $"Total line count: {LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines).End + 1}\n" +
|
||||
// $"Played: {_songPosition} / {TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0)}\n" +
|
||||
// $"Y offset: {_canvasYScrollTransition.Value}\n" +
|
||||
// $"User scroll offset: {_mouseYScrollTransition.Value}",
|
||||
// new Vector2(0, 0), Colors.Red);
|
||||
#if DEBUG && false
|
||||
args.DrawingSession.DrawText(
|
||||
$"Lyrics render start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" +
|
||||
$"Lyrics render size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" +
|
||||
$"Lyrics actual height: {LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines)}\n" +
|
||||
$"Playing line (idx): {_playingLineIndex}\n" +
|
||||
$"Mouse hovering line (idx): {_mouseHoverLineIndex}\n" +
|
||||
$"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" +
|
||||
$"Total line count: {LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines).End + 1}\n" +
|
||||
$"Played: {_songPosition} / {TimeSpan.FromMilliseconds(_gsmtcService.CurrentSongInfo.DurationMs)}\n" +
|
||||
$"Y offset: {_canvasYScrollTransition.Value}\n" +
|
||||
$"User scroll offset: {_mouseYScrollTransition.Value}",
|
||||
new Vector2(0, 0), Colors.Red);
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -459,7 +454,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
var lyricsBg = _lyricsWindowStatus.LyricsBackgroundSettings;
|
||||
var lyricsStyle = _lyricsWindowStatus.LyricsStyleSettings;
|
||||
var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
|
||||
var lyricsData = _mediaSessionsService.CurrentLyricsData;
|
||||
var lyricsData = _gsmtcService.CurrentLyricsData;
|
||||
|
||||
TimeSpan elapsedTime = args.Timing.ElapsedTime;
|
||||
|
||||
@@ -477,22 +472,29 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
#region UpdatePlayingLineIndex
|
||||
|
||||
int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPositionWithOffset.TotalMilliseconds, lyricsData);
|
||||
bool isPlayingLineChanged = newPlayingIndex != _playingLineIndex;
|
||||
_playingLineIndex = newPlayingIndex;
|
||||
int primaryPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPositionWithOffset.TotalMilliseconds, _renderLyricsLines);
|
||||
bool isPrimaryPlayingLineChanged = primaryPlayingIndex != _primaryPlayingLineIndex;
|
||||
_primaryPlayingLineIndex = primaryPlayingIndex;
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateTargetScrollOffset
|
||||
|
||||
if (isPlayingLineChanged || _isLayoutChanged)
|
||||
if (isPrimaryPlayingLineChanged || _isLayoutChanged)
|
||||
{
|
||||
var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_renderLyricsLines, _playingLineIndex);
|
||||
var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_renderLyricsLines, _primaryPlayingLineIndex);
|
||||
if (targetScroll.HasValue) _canvasTargetScrollOffset = targetScroll.Value;
|
||||
|
||||
_canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType);
|
||||
_canvasYScrollTransition.SetDuration(lyricsEffect.LyricsScrollDuration / 1000.0);
|
||||
_canvasYScrollTransition.StartTransition(_canvasTargetScrollOffset, _isLayoutChanged);
|
||||
if (_isLayoutChanged)
|
||||
{
|
||||
_canvasYScrollTransition.JumpTo(_canvasTargetScrollOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
_canvasYScrollTransition.SetDurationMs(lyricsEffect.LyricsScrollDuration);
|
||||
_canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType);
|
||||
_canvasYScrollTransition.Start(_canvasTargetScrollOffset);
|
||||
}
|
||||
}
|
||||
_canvasYScrollTransition.Update(elapsedTime);
|
||||
|
||||
@@ -525,7 +527,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
_renderLyricsLines,
|
||||
_isMouseScrolling ? maxRange.Start : _visibleRange.Start,
|
||||
_isMouseScrolling ? maxRange.End : _visibleRange.End,
|
||||
_playingLineIndex,
|
||||
_primaryPlayingLineIndex,
|
||||
sender.Size.Height,
|
||||
_canvasTargetScrollOffset,
|
||||
lyricsStyle.PlayingLineTopOffset / 100.0,
|
||||
@@ -537,8 +539,9 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
elapsedTime,
|
||||
_isMouseScrolling,
|
||||
_isLayoutChanged,
|
||||
isPlayingLineChanged,
|
||||
_isMouseScrollingChanged
|
||||
isPrimaryPlayingLineChanged,
|
||||
_isMouseScrollingChanged,
|
||||
_songPositionWithOffset.TotalMilliseconds
|
||||
);
|
||||
|
||||
_isMouseScrollingChanged = false;
|
||||
@@ -654,38 +657,32 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private void UpdatePlaybackState(TimeSpan elapsedTime)
|
||||
{
|
||||
if (_mediaSessionsService.CurrentIsPlaying)
|
||||
if (_gsmtcService.CurrentIsPlaying)
|
||||
{
|
||||
_songPosition += elapsedTime;
|
||||
_totalPlayedTime += elapsedTime;
|
||||
_songPositionWithOffset = _songPosition + TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
|
||||
_songPositionWithOffset = _songPosition + TimeSpan.FromMilliseconds(_gsmtcService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPlaybackState()
|
||||
{
|
||||
_songPosition = TimeSpan.Zero;
|
||||
_totalPlayedTime = TimeSpan.Zero;
|
||||
_isLastFMTracked = false;
|
||||
}
|
||||
|
||||
private void UpdateRenderLyricsLines()
|
||||
{
|
||||
_renderLyricsLines = null;
|
||||
_renderLyricsLines = _mediaSessionsService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine()
|
||||
var lines = _gsmtcService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine(x)).ToList();
|
||||
if (lines != null)
|
||||
{
|
||||
LyricsSyllables = x.LyricsSyllables,
|
||||
StartMs = x.StartMs,
|
||||
EndMs = x.EndMs,
|
||||
PhoneticText = x.PhoneticText,
|
||||
OriginalText = x.OriginalText,
|
||||
TranslatedText = x.TranslatedText
|
||||
}).ToList();
|
||||
LyricsLayoutManager.CalculateLanes(lines);
|
||||
}
|
||||
_renderLyricsLines = lines;
|
||||
}
|
||||
|
||||
private async Task ReloadCoverBackgroundResourcesAsync()
|
||||
{
|
||||
if (_mediaSessionsService.AlbumArtBitmapStream is IRandomAccessStream stream)
|
||||
if (_gsmtcService.AlbumArtBitmapStream is IRandomAccessStream stream)
|
||||
{
|
||||
stream.Seek(0);
|
||||
CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(Canvas, stream);
|
||||
@@ -695,26 +692,19 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
|
||||
if (message.PropertyName == nameof(IGSMTCService.CurrentPosition))
|
||||
{
|
||||
var realPosition = message.NewValue;
|
||||
|
||||
var diff = Math.Abs(_songPosition.TotalMilliseconds - realPosition.TotalMilliseconds);
|
||||
var timelineSyncThreshold = _mediaSessionsService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0;
|
||||
var timelineSyncThreshold = _gsmtcService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0;
|
||||
|
||||
// ƫ<><C6AB> or seek
|
||||
if (diff >= timelineSyncThreshold)
|
||||
{
|
||||
_songPosition = realPosition;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˿<EFBFBD>ͷ<EFBFBD><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD> LastFM ͳ<><CDB3>״̬
|
||||
if (_songPosition.TotalSeconds <= 1)
|
||||
{
|
||||
_totalPlayedTime = TimeSpan.Zero;
|
||||
_isLastFMTracked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// <20>϶<EFBFBD><CFB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȴ<EFBFBD><C8B4><EFBFBD><EFBFBD><EFBFBD>
|
||||
@@ -728,9 +718,9 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsData?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentLyricsData))
|
||||
if (message.PropertyName == nameof(IGSMTCService.CurrentLyricsData))
|
||||
{
|
||||
UpdateRenderLyricsLines();
|
||||
_isLayoutChanged = true;
|
||||
@@ -738,11 +728,11 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
public void Receive(PropertyChangedMessage<SongInfo> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
|
||||
if (message.PropertyName == nameof(IGSMTCService.CurrentSongInfo))
|
||||
{
|
||||
ResetPlaybackState();
|
||||
}
|
||||
@@ -895,13 +885,14 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
public void Receive(PropertyChangedMessage<IRandomAccessStream?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapStream))
|
||||
if (message.PropertyName == nameof(IGSMTCService.AlbumArtBitmapStream))
|
||||
{
|
||||
_ = ReloadCoverBackgroundResourcesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,14 @@
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Text="Effect" />
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsWordByWordEffectMode" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.WordByWordEffectMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsWordByWordEffectModeAuto" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsWordByWordEffectModeNever" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsWordByWordEffectModeAlways" />
|
||||
</ComboBox>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 模糊效果 -->
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsBlurEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsBlurEffectEnabled, Mode=TwoWay}" />
|
||||
@@ -108,6 +116,14 @@
|
||||
Minimum="0"
|
||||
Value="{x:Bind LyricsEffectSettings.LyricsFloatAnimationAmount, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsCard x:Uid="LyricsEffectSettingsControlAnimationDuration">
|
||||
<local:ExtendedSlider
|
||||
Default="450"
|
||||
Maximum="2000"
|
||||
Minimum="0"
|
||||
Unit="ms"
|
||||
Value="{x:Bind LyricsEffectSettings.LyricsFloatAnimationDuration, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
xmlns:dev="using:DevWinUI"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:lyricsmodels="using:BetterLyrics.WinUI3.Models.Lyrics"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:BetterLyrics.WinUI3.Models"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
@@ -97,19 +98,6 @@
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</Grid>
|
||||
|
||||
<RichTextBlock
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap">
|
||||
<Paragraph>
|
||||
<Run Text="*" />
|
||||
<Run x:Uid="ArtistsSplitHint" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="; , / ; 、 ," />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -169,7 +157,7 @@
|
||||
<Grid Grid.Column="1">
|
||||
<ListView ItemsSource="{x:Bind ViewModel.LyricsSearchResults, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedLyricsSearchResult, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:LyricsSearchResult">
|
||||
<DataTemplate x:DataType="models:LyricsCacheItem">
|
||||
<ListViewItem IsEnabled="{x:Bind IsFound}">
|
||||
<StackPanel Padding="0,6" Opacity="{x:Bind IsFound, Converter={StaticResource BoolToPartialOpacityConverter}}">
|
||||
<local:PropertyRow
|
||||
@@ -180,17 +168,13 @@
|
||||
<!-- Lyrics search result -->
|
||||
<StackPanel Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind Artist, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow
|
||||
x:Uid="LyricsPageMatchPercentage"
|
||||
Unit="%"
|
||||
Value="{x:Bind MatchPercentage, Mode=OneWay}" />
|
||||
<local:PropertyRow
|
||||
x:Uid="LyricsPageCachePath"
|
||||
Link="{x:Bind SelfPath, TargetNullValue=N/A, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind SelfPath, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<!-- NOT FOUND -->
|
||||
<TextBlock
|
||||
@@ -259,7 +243,7 @@
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<Pivot.HeaderTemplate>
|
||||
<DataTemplate x:DataType="models:LyricsData">
|
||||
<DataTemplate x:DataType="lyricsmodels:LyricsData">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{x:Bind LanguageCode, Mode=OneWay, Converter={StaticResource LanguageCodeToDisplayedNameConverter}}" />
|
||||
<InfoBadge
|
||||
@@ -271,13 +255,13 @@
|
||||
</DataTemplate>
|
||||
</Pivot.HeaderTemplate>
|
||||
<Pivot.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:LyricsData">
|
||||
<DataTemplate x:DataType="lyricsmodels:LyricsData">
|
||||
<ListView
|
||||
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
|
||||
ItemsSource="{x:Bind LyricsLines, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:LyricsLine">
|
||||
<DataTemplate x:DataType="lyricsmodels:LyricsLine">
|
||||
<Grid Margin="0,6" ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -307,7 +291,7 @@
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
</Grid>
|
||||
<local:PropertyRow Grid.Column="1" Value="{x:Bind OriginalText, Mode=OneWay}" />
|
||||
<local:PropertyRow Grid.Column="1" Value="{x:Bind PrimaryText, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Lyrics;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:BetterLyrics.WinUI3.Models"
|
||||
xmlns:settingsmodels="using:BetterLyrics.WinUI3.Models.Settings"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -50,7 +51,7 @@
|
||||
ItemsSource="{x:Bind ViewModel.AppSettings.LocalMediaFolders, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:MediaFolder">
|
||||
<DataTemplate x:DataType="settingsmodels:MediaFolder">
|
||||
<dev:SettingsExpander IsExpanded="True">
|
||||
|
||||
<dev:SettingsExpander.HeaderIcon>
|
||||
@@ -124,7 +125,14 @@
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<dev:SettingsCard Style="{StaticResource DefaultSettingsExpanderItemStyle}">
|
||||
<StackPanel
|
||||
Margin="0,6,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<Button Command="{x:Bind ViewModel.OpenMusicGalleryWindowCommand}">
|
||||
<TextBlock x:Uid="SystemTrayMusicGallery" />
|
||||
</Button>
|
||||
<DropDownButton x:Uid="SettingsPageAddFolderButton">
|
||||
<DropDownButton.Flyout>
|
||||
<MenuFlyout>
|
||||
@@ -169,7 +177,7 @@
|
||||
</MenuFlyout>
|
||||
</DropDownButton.Flyout>
|
||||
</DropDownButton>
|
||||
</dev:SettingsCard>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.System;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
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:dev="using:DevWinUI"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -59,16 +60,16 @@
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<Grid VerticalAlignment="Center" CornerRadius="4">
|
||||
<local:ImageSwitcher
|
||||
x:Name="AlbumArtImageSwitcher"
|
||||
Width="36"
|
||||
Height="36" />
|
||||
Height="36"
|
||||
Source="{x:Bind ViewModel.GSMTCService.AlbumArtBitmapImage, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TitleTextBlock" />
|
||||
<TextBlock Text="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Title, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
x:Name="ArtistsTextBlock"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Artist, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -176,13 +177,13 @@
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
@@ -196,13 +197,13 @@
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
@@ -215,12 +216,16 @@
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
<!-- 播放队列按钮 -->
|
||||
<ToggleButton
|
||||
<Button
|
||||
Click="PlayingQueueButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostToggleButtonStyle}"
|
||||
Visibility="{x:Bind ShowPlayingQueueButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
Style="{StaticResource GhostButtonStyle}"
|
||||
Visibility="{x:Bind ShowPlayingQueueButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPagePlayingQueue" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -231,6 +236,19 @@
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
|
||||
<!-- Stop media session -->
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.StopTrackCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}"
|
||||
Visibility="{x:Bind ShowStopButton, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageStopTrack" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
|
||||
<!-- Volume -->
|
||||
<Button Click="VolumeButton_Click" Style="{StaticResource GhostButtonStyle}">
|
||||
<Grid>
|
||||
@@ -402,10 +420,11 @@
|
||||
Margin="0,-14,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Maximum="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}"
|
||||
Maximum="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}"
|
||||
Minimum="0"
|
||||
Style="{StaticResource GhostSliderStyle}"
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}" />
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
|
||||
Value="{x:Bind ViewModel.GSMTCService.CurrentPosition.TotalSeconds, Mode=OneWay}" />
|
||||
|
||||
<Grid
|
||||
x:Name="TimelineSliderLyricsLineInfo"
|
||||
@@ -428,7 +447,7 @@
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.TimelineSliderThumbLyricsLine.StartMs, Converter={StaticResource MillisecondsToFormattedTimeConverter}, Mode=OneWay}" />
|
||||
<!-- TODO 原文翻译共同显示 -->
|
||||
<TextBlock Margin="0,0,0,2" Text="{x:Bind ViewModel.TimelineSliderThumbLyricsLine.OriginalText, Mode=OneWay}" />
|
||||
<TextBlock Margin="0,0,0,2" Text="{x:Bind ViewModel.TimelineSliderThumbLyricsLine.PrimaryText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid
|
||||
|
||||
@@ -1,35 +1,27 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
|
||||
// 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.Controls;
|
||||
|
||||
public sealed partial class NowPlayingBar : UserControl,
|
||||
IRecipient<PropertyChangedMessage<SongInfo?>>,
|
||||
IRecipient<PropertyChangedMessage<BitmapImage?>>,
|
||||
IRecipient<PropertyChangedMessage<TimeSpan>>
|
||||
public sealed partial class NowPlayingBar : UserControl
|
||||
{
|
||||
public NowPlayingBarViewModel ViewModel => (NowPlayingBarViewModel)DataContext;
|
||||
|
||||
public event EventHandler? SongInfoTapped;
|
||||
public event EventHandler? TimeTapped;
|
||||
public event EventHandler? PlayQueueButtonClick;
|
||||
|
||||
public bool ShowTime
|
||||
{
|
||||
@@ -64,6 +56,15 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
set { SetValue(ShowPlaybackOrderButtonProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowStopButtonProperty =
|
||||
DependencyProperty.Register(nameof(ShowStopButton), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
|
||||
|
||||
public bool ShowStopButton
|
||||
{
|
||||
get { return (bool)GetValue(ShowStopButtonProperty); }
|
||||
set { SetValue(ShowStopButtonProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowPlaybackOrderButtonProperty =
|
||||
DependencyProperty.Register(nameof(ShowPlaybackOrderButton), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
|
||||
|
||||
@@ -76,15 +77,6 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
public static readonly DependencyProperty PlaybackOrderProperty =
|
||||
DependencyProperty.Register(nameof(PlaybackOrder), typeof(PlaybackOrder), typeof(NowPlayingBar), new PropertyMetadata(PlaybackOrder.RepeatAll));
|
||||
|
||||
public bool IsPlayingQueueOpened
|
||||
{
|
||||
get { return (bool)GetValue(IsPlayingQueueOpenedProperty); }
|
||||
set { SetValue(IsPlayingQueueOpenedProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsPlayingQueueOpenedProperty =
|
||||
DependencyProperty.Register(nameof(IsPlayingQueueOpened), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
|
||||
|
||||
public bool IsCompactMode
|
||||
{
|
||||
get { return (bool)GetValue(IsCompactModeProperty); }
|
||||
@@ -109,8 +101,6 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<NowPlayingBarViewModel>();
|
||||
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
}
|
||||
|
||||
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
@@ -206,7 +196,7 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
var grid = (Grid)sender;
|
||||
var pos = e.GetCurrentPoint(grid).Position;
|
||||
var ratio = pos.X / grid.ActualWidth;
|
||||
ViewModel.MediaSessionsService.ChangePosition(TimelineSlider.Maximum * ratio);
|
||||
ViewModel.GSMTCService.ChangePosition(TimelineSlider.Maximum * ratio);
|
||||
}
|
||||
|
||||
private void TimelineSliderOverlay_PointerMoved(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
@@ -303,45 +293,11 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
|
||||
private void PlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IsPlayingQueueOpened = !IsPlayingQueueOpened;
|
||||
PlayQueueButtonClick?.Invoke(sender, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PlaybackOrderButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PlaybackOrder = PlaybackOrder.GetNext();
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
|
||||
{
|
||||
TitleTextBlock.Text = message.NewValue?.Title;
|
||||
ArtistsTextBlock.Text = message.NewValue?.DisplayArtists;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void Receive(PropertyChangedMessage<BitmapImage?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapImage))
|
||||
{
|
||||
AlbumArtImageSwitcher.Source = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
|
||||
{
|
||||
TimelineSlider.Value = message.NewValue.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.PatronControl"
|
||||
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.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Margin="12,8">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind PatronName, Mode=OneWay}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorTertiaryBrush}" Text="{x:Bind Date, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
// 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.Controls
|
||||
{
|
||||
public sealed partial class PatronControl : UserControl
|
||||
{
|
||||
public string PatronName
|
||||
{
|
||||
get { return (string)GetValue(PatronNameProperty); }
|
||||
set { SetValue(PatronNameProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PatronNameProperty =
|
||||
DependencyProperty.Register(nameof(PatronName), typeof(string), typeof(PatronControl), new PropertyMetadata(""));
|
||||
|
||||
public string Date
|
||||
{
|
||||
get { return (string)GetValue(DateProperty); }
|
||||
set { SetValue(DateProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DateProperty =
|
||||
DependencyProperty.Register(nameof(Date), typeof(string), typeof(PatronControl), new PropertyMetadata(""));
|
||||
|
||||
public PatronControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
142
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/PlayQueue.xaml
Normal file
142
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/PlayQueue.xaml
Normal file
@@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.PlayQueue"
|
||||
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:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<Grid.TranslationTransition>
|
||||
<Vector3Transition />
|
||||
</Grid.TranslationTransition>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="12,12,12,0">
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPagePlayingQueue"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Margin="12,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=OneWay, Converter={StaticResource IndexToDisplayConverter}}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
|
||||
<TextBlock Text="{x:Bind ViewModel.SMTCService.TrackPlayingQueue.Count, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Scroll to playing item -->
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Click="ScrollToPlayingItemButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageScrollToPlayingItem" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<!-- Empty play queue -->
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
Click="EmptyPlayingQueueButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageEmptyPlayingQueue" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
|
||||
<NavigationViewItemSeparator Grid.Row="2" />
|
||||
|
||||
<ListView
|
||||
x:Name="PlayingQueueListView"
|
||||
Grid.Row="3"
|
||||
ItemsSource="{x:Bind ViewModel.SMTCService.TrackPlayingQueue, Mode=OneWay}"
|
||||
SelectedIndex="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Padding="0,6">
|
||||
<Grid Tapped="PlayingQueueListVireItemGrid_Tapped">
|
||||
<StackPanel Margin="0,0,36,0">
|
||||
<TextBlock Text="{Binding Track.Title}" TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Track.Artist}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid HorizontalAlignment="Right">
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Click="RemoveFromPlayingQueueButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageRemoveFromPlayingQueue" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<Grid Grid.Row="3">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SMTCService.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SMTCService.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<Image MaxWidth="100" Source="/Assets/EmptyBox.png" />
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPagePlayingQueueEmpty"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,94 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
// 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.Controls
|
||||
{
|
||||
public sealed partial class PlayQueue : UserControl, IRecipient<PropertyChangedMessage<int>>
|
||||
{
|
||||
public PlayQueueViewModel ViewModel => (PlayQueueViewModel)DataContext;
|
||||
public PlayQueue()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<PlayQueueViewModel>();
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
}
|
||||
|
||||
private void ScrollToPlayingItem()
|
||||
{
|
||||
if (PlayingQueueListView == null) return;
|
||||
|
||||
var targetItem = ViewModel.SMTCService.TrackPlayingQueue
|
||||
.ElementAtOrDefault(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
if (targetItem == null) return;
|
||||
|
||||
PlayingQueueListView.ScrollIntoView(targetItem);
|
||||
}
|
||||
|
||||
private void ScrollToPlayingItemButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ScrollToPlayingItem();
|
||||
}
|
||||
|
||||
private async void PlayingQueueListVireItemGrid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
|
||||
await ViewModel.SMTCService.PlayTrackAsync(item);
|
||||
}
|
||||
|
||||
private async void RemoveFromPlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool playNext = false;
|
||||
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
|
||||
int index = ViewModel.SMTCService.TrackPlayingQueue.IndexOf(item);
|
||||
if (item == PlayingQueueListView.SelectedItem)
|
||||
{
|
||||
playNext = true;
|
||||
}
|
||||
ViewModel.SMTCService.TrackPlayingQueue.Remove(item);
|
||||
if (playNext)
|
||||
{
|
||||
if (ViewModel.SMTCService.TrackPlayingQueue.Count == 0)
|
||||
{
|
||||
index = -1;
|
||||
}
|
||||
else if (index >= ViewModel.SMTCService.TrackPlayingQueue.Count)
|
||||
{
|
||||
index = ViewModel.SMTCService.TrackPlayingQueue.Count - 1;
|
||||
}
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = index;
|
||||
await ViewModel.SMTCService.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private async void EmptyPlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.SMTCService.TrackPlayingQueue.Clear();
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = -1;
|
||||
await ViewModel.SMTCService.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is MusicGallerySettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(MusicGallerySettings.PlayQueueIndex))
|
||||
{
|
||||
ScrollToPlayingItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:BetterLyrics.WinUI3.Models"
|
||||
xmlns:settingsmodels="using:BetterLyrics.WinUI3.Models.Settings"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -60,7 +61,7 @@
|
||||
ItemsSource="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedMediaSourceProvider, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:MediaSourceProviderInfo">
|
||||
<DataTemplate x:DataType="settingsmodels:MediaSourceProviderInfo">
|
||||
<Grid Padding="2,4" ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -172,7 +173,7 @@
|
||||
<ScalarTransition />
|
||||
</ListView.OpacityTransition>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:AlbumArtSearchProviderInfo">
|
||||
<DataTemplate x:DataType="settingsmodels:AlbumArtSearchProviderInfo">
|
||||
<dev:SettingsCard Header="{Binding Provider, Converter={StaticResource AlbumArtSearchProviderToDisplayNameConverter}, Mode=OneWay}">
|
||||
<dev:SettingsCard.HeaderIcon>
|
||||
<FontIcon FontFamily="Segoe UI Symbol" Glyph="⠿" />
|
||||
@@ -219,7 +220,7 @@
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:LyricsSearchProviderInfo">
|
||||
<DataTemplate x:DataType="settingsmodels:LyricsSearchProviderInfo">
|
||||
<Grid>
|
||||
<dev:SettingsExpander Header="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}, Mode=OneWay}" IsExpanded="{Binding IsMatchingThresholdOverwritten, Mode=OneWay}">
|
||||
<dev:SettingsExpander.HeaderIcon>
|
||||
@@ -227,6 +228,9 @@
|
||||
</dev:SettingsExpander.HeaderIcon>
|
||||
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
|
||||
<dev:SettingsExpander.Items>
|
||||
<dev:SettingsCard x:Uid="LyricsSearchControlIgnoreCache">
|
||||
<CheckBox IsChecked="{Binding IgnoreCacheWhenSearching, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsCard x:Uid="SettingsPageOverwriteMatchingThreshold">
|
||||
<ToggleSwitch IsOn="{Binding IsMatchingThresholdOverwritten, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
@@ -314,8 +318,8 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<local:PropertyRow x:Uid="SettingsPagePlaybackSource" Value="{x:Bind ViewModel.MediaSessionsService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPagePlaybackSourceID" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPagePlaybackSource" Value="{x:Bind ViewModel.GSMTCService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPagePlaybackSourceID" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
@@ -325,10 +329,10 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DurationMs, TargetNullValue=N/A, Converter={StaticResource MillisecondsToFormattedTimeConverter}, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Artist, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.DurationMs, TargetNullValue=N/A, Converter={StaticResource MillisecondsToFormattedTimeConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
@@ -338,26 +342,22 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageLanguageCode" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsData.LanguageCode, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource LanguageCodeToDisplayedNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Artist, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageLanguageCode" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsData.LanguageCode, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource LanguageCodeToDisplayedNameConverter}}" />
|
||||
<local:PropertyRow
|
||||
x:Uid="LyricsPageLyricsProviderPrefix"
|
||||
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, TargetNullValue=N/A, Mode=OneWay}"
|
||||
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageTransliterationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.TransliterationProvider, Mode=OneWay, Converter={StaticResource TransliterationSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.TranslationProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
|
||||
Link="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Reference, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}"
|
||||
ToolTipService.ToolTip="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Reference, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}"
|
||||
Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageTransliterationProviderPrefix" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.TransliterationProvider, Mode=OneWay, Converter={StaticResource TransliterationSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.TranslationProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow
|
||||
x:Uid="LyricsPageMatchPercentage"
|
||||
Unit="%"
|
||||
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" />
|
||||
<local:PropertyRow
|
||||
x:Uid="LyricsPageCachePath"
|
||||
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
@@ -365,10 +365,6 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ListenOnNewPlaybackSource, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageForceWordByWordEffect">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IsForceWordByWordEffect, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- Lyrics translation -->
|
||||
<TextBlock x:Uid="SettingsPageTranslation" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
<dev:SettingsExpander x:Uid="LyricsPageTranslationEnabled" IsExpanded="True">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
@@ -142,7 +142,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
ProgressBar.Visibility = visibility;
|
||||
}
|
||||
|
||||
|
||||
private void CheckPathForWarning()
|
||||
{
|
||||
string? path = PathBox.Text?.Trim();
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
x:Class="BetterLyrics.WinUI3.Controls.StatsDashboardControl"
|
||||
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:converters="using:BetterLyrics.WinUI3.Converter"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dev="using:DevWinUI"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.WinUI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:BetterLyrics.WinUI3.Models"
|
||||
xmlns:statsmodels="using:BetterLyrics.WinUI3.Models.Stats"
|
||||
@@ -14,7 +17,7 @@
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="StatsCardStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="{ThemeResource LayerFillColorDefaultBrush}" />
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
@@ -23,38 +26,98 @@
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="0,24,0,0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="20,0">
|
||||
<Pivot x:Name="TimeRangePivot" SelectionChanged="Pivot_SelectionChanged">
|
||||
<PivotItem x:Uid="StatsDashboardControlToday" Tag="Day" />
|
||||
<PivotItem x:Uid="StatsDashboardControlThisWeek" Tag="Week" />
|
||||
<PivotItem x:Uid="StatsDashboardControlThisMonth" Tag="Month" />
|
||||
<PivotItem x:Uid="StatsDashboardControlThisQuarter" Tag="Quarter" />
|
||||
<PivotItem x:Uid="StatsDashboardControlThisYear" Tag="Year" />
|
||||
</Pivot>
|
||||
<ProgressBar
|
||||
Grid.Row="0"
|
||||
Background="Transparent"
|
||||
IsIndeterminate="{x:Bind ViewModel.IsLoading, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.IsLoading, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<Grid Grid.Row="1" Visibility="{x:Bind ViewModel.GSMTCService.IsScrobbled, Mode=OneWay, Converter={StaticResource BoolNegationToVisibilityConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<InfoBar
|
||||
x:Uid="StatsDashboardControlRecording"
|
||||
Grid.Row="0"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Message="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Title, Mode=OneWay}" />
|
||||
<ProgressBar
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
Maximum="{x:Bind ViewModel.GSMTCService.TargetScrobbledDuration.TotalSeconds, Mode=OneWay}"
|
||||
ShowPaused="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
|
||||
Value="{x:Bind ViewModel.GSMTCService.ScrobbledDuration.TotalSeconds, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
<ScrollViewer Grid.Row="1" Padding="20,0">
|
||||
<controls:WrapPanel
|
||||
Grid.Row="2"
|
||||
Margin="36,36,36,12"
|
||||
HorizontalSpacing="12"
|
||||
Orientation="Horizontal"
|
||||
VerticalSpacing="12">
|
||||
<ComboBox
|
||||
x:Uid="StatsDashboardControlTimeRange"
|
||||
Header="Time Range"
|
||||
SelectedIndex="{x:Bind ViewModel.SelectedTimeRange, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlToday" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisWeek" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisMonth" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisQuarter" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisYear" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlCustom" />
|
||||
</ComboBox>
|
||||
|
||||
<CalendarDatePicker
|
||||
x:Uid="StatsDashboardControlStart"
|
||||
Date="{x:Bind ViewModel.CustomStartDate, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}" />
|
||||
<TimePicker
|
||||
VerticalAlignment="Bottom"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}"
|
||||
Time="{x:Bind ViewModel.CustomStartTime, Mode=TwoWay}" />
|
||||
<CalendarDatePicker
|
||||
x:Uid="StatsDashboardControlEnd"
|
||||
Date="{x:Bind ViewModel.CustomEndDate, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}" />
|
||||
<TimePicker
|
||||
VerticalAlignment="Bottom"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}"
|
||||
Time="{x:Bind ViewModel.CustomEndTime, Mode=TwoWay}" />
|
||||
<Button
|
||||
VerticalAlignment="Bottom"
|
||||
Command="{x:Bind ViewModel.RefreshDataCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}" />
|
||||
</controls:WrapPanel>
|
||||
|
||||
<ScrollViewer Grid.Row="3" Padding="36,0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="0,20,0,0">
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 总播放时长 -->
|
||||
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel
|
||||
@@ -71,7 +134,7 @@
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.TotalDuration.TotalHours, Mode=OneWay}" />
|
||||
Text="{x:Bind ViewModel.TotalDuration.TotalHours, Mode=OneWay, Converter={StaticResource DoubleToDecimalConverter}}" />
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
VerticalAlignment="Bottom"
|
||||
@@ -83,7 +146,6 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 总播放歌曲次数 -->
|
||||
<Border Grid.Column="1" Style="{StaticResource StatsCardStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel
|
||||
@@ -100,7 +162,6 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Top source -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Margin="0,0,0,12"
|
||||
@@ -121,25 +182,99 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<!-- Activity by hour -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0,0,0,12"
|
||||
Style="{StaticResource StatsCardStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Margin="0,0,0,12"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock x:Uid="StatsDashboardControlActivityByHour" Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1" Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlMostActive"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.PeakHourText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1">
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlLeastActive"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.QuietHourText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<lvc:CartesianChart
|
||||
Grid.Row="2"
|
||||
Height="180"
|
||||
Margin="0,8,0,0"
|
||||
Background="Transparent"
|
||||
TooltipPosition="Top">
|
||||
|
||||
<lvc:CartesianChart.XAxes>
|
||||
<lvc:AxesCollection>
|
||||
<lvc:XamlAxis Labels="{x:Bind ViewModel.HourlyXAxisLabels, Mode=OneWay}" TextSize="{StaticResource BodyTextBlockFontSize}" />
|
||||
</lvc:AxesCollection>
|
||||
</lvc:CartesianChart.XAxes>
|
||||
|
||||
<lvc:CartesianChart.YAxes>
|
||||
<lvc:AxesCollection>
|
||||
<lvc:XamlAxis
|
||||
x:Uid="StatsDashboardControlTrackCountAxis"
|
||||
NameTextSize="{StaticResource BodyTextBlockFontSize}"
|
||||
ShowSeparatorLines="False"
|
||||
TextSize="{StaticResource BodyTextBlockFontSize}" />
|
||||
</lvc:AxesCollection>
|
||||
</lvc:CartesianChart.YAxes>
|
||||
|
||||
<lvc:CartesianChart.Series>
|
||||
<lvc:SeriesCollection>
|
||||
<lvc:XamlColumnSeries
|
||||
x:Name="HourlySeries"
|
||||
Rx="4"
|
||||
Ry="4"
|
||||
Values="{x:Bind ViewModel.HourlySeriesValues, Mode=OneWay}" />
|
||||
</lvc:SeriesCollection>
|
||||
</lvc:CartesianChart.Series>
|
||||
|
||||
</lvc:CartesianChart>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Top artists and sources -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1.5*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Top artists -->
|
||||
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlTopArtists"
|
||||
Margin="0,0,0,12"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind ViewModel.TopArtists, Mode=OneWay}">
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.TopArtists, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="statsmodels:ArtistPlayCount">
|
||||
<Grid Margin="0,4">
|
||||
@@ -163,67 +298,81 @@
|
||||
FontWeight="SemiBold">
|
||||
<Run Text="{x:Bind PlayCount}" />
|
||||
<Run
|
||||
x:Uid="StatsDashboardControlTrackCountText"
|
||||
FontSize="10"
|
||||
FontWeight="Normal"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Text="plays" />
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Top sources -->
|
||||
<!-- Top tracks -->
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,12"
|
||||
Style="{StaticResource StatsCardStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlSources"
|
||||
x:Uid="StatsDashboardControlTopSongs"
|
||||
Margin="0,0,0,12"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind ViewModel.PlayerStats, Mode=OneWay}">
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.TopSongs, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:PlayerStatDisplayItem">
|
||||
<DataTemplate x:DataType="statsmodels:SongPlayCount">
|
||||
<Grid Margin="0,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
Width="40"
|
||||
Height="40"
|
||||
Margin="0,0,12,0"
|
||||
Background="{ThemeResource LayerFillColorAltBrush}"
|
||||
CornerRadius="4">
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Style="{ThemeResource BodyStrongTextBlockStyle}" Text="{x:Bind Title}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Artist}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
FontSize="13"
|
||||
Style="{ThemeResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind PlayerName}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold">
|
||||
<Run Text="{x:Bind PlayCount}" />
|
||||
<Run
|
||||
x:Uid="StatsDashboardControlTrackCountText"
|
||||
FontSize="10"
|
||||
FontWeight="Normal"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Text="plays" />
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Top song -->
|
||||
<!-- 播放源分布 -->
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Grid.Row="3"
|
||||
Margin="0,0,0,20"
|
||||
Style="{StaticResource StatsCardStyle}">
|
||||
<Grid>
|
||||
@@ -232,71 +381,30 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlTopSongs"
|
||||
x:Uid="StatsDashboardControlSources"
|
||||
Margin="0,0,0,12"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<ListView
|
||||
<lvc:PieChart
|
||||
Grid.Row="1"
|
||||
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
|
||||
ItemsSource="{x:Bind ViewModel.TopSongs, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="statsmodels:SongPlayCount">
|
||||
<Grid Margin="0,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
Width="40"
|
||||
Height="40"
|
||||
Margin="0,0,12,0"
|
||||
Background="{ThemeResource LayerFillColorAltBrush}"
|
||||
CornerRadius="4">
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
MinHeight="250"
|
||||
Background="Transparent"
|
||||
LegendPosition="Bottom"
|
||||
LegendTextSize="{StaticResource BodyTextBlockFontSize}"
|
||||
Series="{x:Bind ViewModel.SourceSeries, Mode=OneWay}"
|
||||
TooltipPosition="Center" />
|
||||
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Style="{ThemeResource BodyStrongTextBlockStyle}" Text="{x:Bind Title}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Artist}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold">
|
||||
<Run Text="{x:Bind PlayCount}" />
|
||||
<Run
|
||||
FontSize="10"
|
||||
FontWeight="Normal"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Text="plays" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
<!--<Button
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Command="{x:Bind ViewModel.GenerateTestDataCommand}"
|
||||
Content="Generate test data"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
Content="Generate test data" />-->
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -1,20 +1,6 @@
|
||||
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 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 BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
@@ -29,30 +15,6 @@ public sealed partial class StatsDashboardControl : UserControl
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<StatsDashboardControlViewModel>();
|
||||
this.Loaded += StatsDashboardControl_Loaded;
|
||||
}
|
||||
|
||||
private async void StatsDashboardControl_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
await ViewModel.LoadDataAsync(StatsRange.Day);
|
||||
}
|
||||
|
||||
private async void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel == null) return;
|
||||
|
||||
if (TimeRangePivot.SelectedItem is PivotItem item && item.Tag is string tag)
|
||||
{
|
||||
var range = tag switch
|
||||
{
|
||||
"Day" => StatsRange.Day,
|
||||
"Week" => StatsRange.Week,
|
||||
"Month" => StatsRange.Month,
|
||||
"Quarter" => StatsRange.Quarter,
|
||||
"Year" => StatsRange.Year,
|
||||
_ => StatsRange.Day
|
||||
};
|
||||
await ViewModel.LoadDataAsync(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class DoubleToDecimalConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value == null) return string.Empty;
|
||||
|
||||
if (double.TryParse(value.ToString(), out double number))
|
||||
{
|
||||
int decimalPlaces = 2;
|
||||
if (parameter != null && int.TryParse(parameter.ToString(), out int parsedParams))
|
||||
{
|
||||
decimalPlaces = parsedParams;
|
||||
}
|
||||
|
||||
return number.ToString($"F{decimalPlaces}");
|
||||
}
|
||||
|
||||
return value.ToString() ?? "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class UriStringToDecodedAbsoluteUri : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string uriString)
|
||||
{
|
||||
return uriString.ToDecodedAbsoluteUri();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum AutoScanInterval
|
||||
{
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum Language
|
||||
{
|
||||
FollowSystem,
|
||||
English,
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
Japanese,
|
||||
Korean,
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LocalSearchTargetProps
|
||||
{
|
||||
LyricsOnly,
|
||||
LyricsAndAlbumArt,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum SettingsStoreType
|
||||
{
|
||||
Container,
|
||||
JSON
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum ShortcutID
|
||||
public enum ShortcutId
|
||||
{
|
||||
LyricsWindowShowOrHide,
|
||||
LyricsWindowSwitch,
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum StatsRange
|
||||
{
|
||||
Day,
|
||||
Week,
|
||||
Month,
|
||||
Quarter,
|
||||
Year
|
||||
Today,
|
||||
ThisWeek,
|
||||
ThisMonth,
|
||||
ThisQuarter,
|
||||
ThisYear,
|
||||
Custom
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum WordByWordEffectMode
|
||||
{
|
||||
Auto,
|
||||
Never,
|
||||
Always,
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, List<Color> albumArtLightAccentColors, List<Color> albumArtDarkAccentColors) : EventArgs
|
||||
{
|
||||
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
|
||||
public List<Color> AlbumArtLightAccentColors { get; set; } = albumArtLightAccentColors;
|
||||
public List<Color> AlbumArtDarkAccentColors { get; set; } = albumArtDarkAccentColors;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType) : EventArgs
|
||||
{
|
||||
public WatcherChangeTypes ChangeType { get; } = changeType;
|
||||
public string FilePath { get; } = filePath;
|
||||
public string Folder { get; } = folder;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class LyricsChangedEventArgs(LyricsData? lyricsData) : EventArgs
|
||||
{
|
||||
public LyricsData? LyricsData { get; } = lyricsData;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class MediaSourceProvidersInfoEventArgs(List<MediaSourceProviderInfo> sessionIds) : EventArgs
|
||||
{
|
||||
public List<MediaSourceProviderInfo> MediaSourceProviersInfo { get; set; } = sessionIds;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Lyrics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
@@ -20,7 +19,7 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
|
||||
OriginalText = "● ● ●",
|
||||
PrimaryText = "● ● ●",
|
||||
},
|
||||
],
|
||||
LanguageCode = "N/A",
|
||||
@@ -38,12 +37,12 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
if (transLine != null)
|
||||
{
|
||||
// 此处 transLine.OriginalText 指翻译中的“原文”属性
|
||||
line.TranslatedText = transLine.OriginalText;
|
||||
line.SecondaryText = transLine.PrimaryText;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有匹配的翻译
|
||||
line.TranslatedText = "";
|
||||
line.SecondaryText = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,12 +58,12 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
if (transLine != null)
|
||||
{
|
||||
// 此处 transLine.OriginalText 指音译中的“原文”属性
|
||||
line.PhoneticText = transLine.OriginalText;
|
||||
line.TertiaryText = transLine.PrimaryText;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有匹配的音译
|
||||
line.PhoneticText = "";
|
||||
line.TertiaryText = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,11 +76,11 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
if (i >= translationArr.Count)
|
||||
{
|
||||
line.TranslatedText = ""; // No translation available, keep empty
|
||||
line.SecondaryText = ""; // No translation available, keep empty
|
||||
}
|
||||
else
|
||||
{
|
||||
line.TranslatedText = translationArr[i];
|
||||
line.SecondaryText = translationArr[i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
@@ -95,11 +94,11 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
if (i >= transliterationArr.Count)
|
||||
{
|
||||
line.PhoneticText = ""; // No transliteration available, keep empty
|
||||
line.TertiaryText = ""; // No transliteration available, keep empty
|
||||
}
|
||||
else
|
||||
{
|
||||
line.PhoneticText = transliterationArr[i];
|
||||
line.TertiaryText = transliterationArr[i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
@@ -8,21 +6,6 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
extension(LyricsSearchProvider provider)
|
||||
{
|
||||
public string GetCacheDirectory() => provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => PathHelper.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => PathHelper.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AppleMusic => PathHelper.AppleMusicCacheDirectory,
|
||||
LyricsSearchProvider.LocalMusicFile => PathHelper.LocalMusicCacheDirectory,
|
||||
LyricsSearchProvider.LocalLrcFile => PathHelper.LocalLrcCacheDirectory,
|
||||
LyricsSearchProvider.LocalEslrcFile => PathHelper.LocalEslrcCacheDirectory,
|
||||
LyricsSearchProvider.LocalTtmlFile => PathHelper.LocalTtmlCacheDirectory,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(provider)),
|
||||
};
|
||||
|
||||
public LyricsFormat GetLyricsFormat() => provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => LyricsFormat.Lrc,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Entities;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class SongInfoExtensions
|
||||
{
|
||||
public static SongInfo Placeholder => new SongInfo
|
||||
public static SongInfo Placeholder => new()
|
||||
{
|
||||
Title = "N/A",
|
||||
Album = "N/A",
|
||||
Artists = ["N/A"],
|
||||
Artist = "N/A",
|
||||
};
|
||||
|
||||
extension(SongInfo songInfo)
|
||||
@@ -20,9 +24,9 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
return songInfo;
|
||||
}
|
||||
|
||||
public SongInfo WithArtist(string[] value)
|
||||
public SongInfo WithArtist(string value)
|
||||
{
|
||||
songInfo.Artists = value;
|
||||
songInfo.Artist = value;
|
||||
return songInfo;
|
||||
}
|
||||
|
||||
@@ -39,14 +43,31 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
return new PlayHistoryItem
|
||||
{
|
||||
Title = songInfo.Title,
|
||||
Artist = songInfo.DisplayArtists,
|
||||
Artist = songInfo.Artist,
|
||||
Album = songInfo.Album,
|
||||
PlayerId = songInfo.PlayerId ?? "N/A",
|
||||
TotalDurationMs = songInfo.DurationMs,
|
||||
DurationPlayedMs = actualPlayedMs,
|
||||
StartedAt = DateTime.Now.AddMilliseconds(-actualPlayedMs)
|
||||
StartedAt = DateTime.FromBinary(songInfo.StartedAt)
|
||||
};
|
||||
}
|
||||
|
||||
public string GetCacheKey()
|
||||
{
|
||||
string title = songInfo.Title?.Trim() ?? "";
|
||||
string album = songInfo.Album?.Trim() ?? "";
|
||||
|
||||
string artists = songInfo.Artist?.Trim() ?? "";
|
||||
|
||||
long seconds = (long)Math.Round(songInfo.Duration);
|
||||
string durationPart = seconds.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
string rawKey = $"{title}|{artists}|{album}|{durationPart}";
|
||||
|
||||
using var sha256 = SHA256.Create();
|
||||
byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(rawKey));
|
||||
return Convert.ToHexString(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
@@ -75,6 +76,18 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string ToDecodedAbsoluteUri()
|
||||
{
|
||||
if (string.IsNullOrEmpty(str)) return "";
|
||||
try
|
||||
{
|
||||
var u = new Uri(str);
|
||||
return u.IsFile ? u.LocalPath : System.Net.WebUtility.UrlDecode(u.AbsoluteUri);
|
||||
}
|
||||
catch { return str; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ColorHelper
|
||||
{
|
||||
public static Color GetSystemAccentColor()
|
||||
{
|
||||
if (Application.Current.Resources.TryGetValue("SystemAccentColor", out var resource) &&
|
||||
resource is Color uiColor)
|
||||
{
|
||||
return uiColor;
|
||||
}
|
||||
return Color.FromArgb(255, 0, 120, 215);
|
||||
}
|
||||
|
||||
public static ElementTheme GetElementThemeFromBackgroundColor(Color backgroundColor)
|
||||
{
|
||||
// 计算亮度(YIQ公式)
|
||||
|
||||
@@ -56,6 +56,57 @@
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://learn.microsoft.com/zh-cn/dotnet/standard/io/how-to-copy-directories
|
||||
/// </summary>
|
||||
/// <param name="sourceDir"></param>
|
||||
/// <param name="destinationDir"></param>
|
||||
/// <param name="recursive"></param>
|
||||
/// <exception cref="DirectoryNotFoundException"></exception>
|
||||
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
|
||||
{
|
||||
// Get information about the source directory
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
// Check if the source directory exists
|
||||
if (!dir.Exists)
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
|
||||
// Cache directories before we start copying
|
||||
DirectoryInfo[] dirs = dir.GetDirectories();
|
||||
|
||||
// Create the destination directory
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
// Get the files in the source directory and copy to the destination directory
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
{
|
||||
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||
|
||||
CopyLockedFile(file.FullName, targetFilePath);
|
||||
}
|
||||
|
||||
// If recursive and copying subdirectories, recursively call this method
|
||||
if (recursive)
|
||||
{
|
||||
foreach (DirectoryInfo subDir in dirs)
|
||||
{
|
||||
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
||||
CopyDirectory(subDir.FullName, newDestinationDir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyLockedFile(string sourcePath, string targetPath)
|
||||
{
|
||||
using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
using (var destStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
sourceStream.CopyTo(destStream);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -53,22 +52,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static LyricsSearchResult? ReadLyricsCache(SongInfo songInfo, LyricsSearchProvider lyricsSearchProvider)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(
|
||||
lyricsSearchProvider.GetCacheDirectory(),
|
||||
SanitizeFileName($"{songInfo.ToFileName()}.json"));
|
||||
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
var json = File.ReadAllText(cacheFilePath);
|
||||
var data = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LyricsSearchResult);
|
||||
data?.SelfPath = cacheFilePath;
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
|
||||
@@ -79,19 +62,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void WriteLyricsCache(SongInfo songInfo, LyricsSearchResult lyricsSearchResult)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(
|
||||
lyricsSearchResult.Provider.GetCacheDirectory(),
|
||||
SanitizeFileName($"{songInfo.ToFileName()}.json"));
|
||||
lyricsSearchResult.SelfPath = cacheFilePath;
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(lyricsSearchResult, SourceGenerationContext.Default.LyricsSearchResult);
|
||||
File.WriteAllText(cacheFilePath, json);
|
||||
}
|
||||
|
||||
public static void WriteAlbumArtCache(SongInfo songInfo, byte[] img, string format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{songInfo.DisplayArtists} - {songInfo.Album}{format}"));
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{songInfo.Artist} - {songInfo.Album}{format}"));
|
||||
File.WriteAllBytes(cacheFilePath, img);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System.Linq;
|
||||
|
||||
public static class FolderTreeBuilder
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@ using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using NTextCat;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Windows.Globalization;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Entities;
|
||||
using F23.StringSimilarity;
|
||||
using System;
|
||||
using System.IO;
|
||||
@@ -17,21 +18,31 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
// JaroWinkler 适合短字符串匹配
|
||||
private static readonly JaroWinkler _algo = new();
|
||||
|
||||
public static int CalculateScore(SongInfo local, LyricsSearchResult remote)
|
||||
public static int CalculateScore(SongInfo songInfo, LyricsCacheItem remote)
|
||||
{
|
||||
if (local == null || remote == null) return 0;
|
||||
return CalculateScore(songInfo, remote.Title, remote.Artist, remote.Album, remote.Duration * 1000);
|
||||
}
|
||||
|
||||
public static int CalculateScore(SongInfo songInfo, FilesIndexItem local)
|
||||
{
|
||||
return CalculateScore(songInfo, local.Title, local.Artist, local.Album, local.Duration * 1000, local.FileName);
|
||||
}
|
||||
|
||||
public static int CalculateScore(
|
||||
SongInfo songInfo,
|
||||
string? compareTitle, string? compareArtist, string? compareAlbum, double? compareDurationMs, string? compareFileName = null)
|
||||
{
|
||||
double totalScore = 0;
|
||||
|
||||
bool localHasMetadata = !string.IsNullOrWhiteSpace(local.Title);
|
||||
bool remoteHasMetadata = !string.IsNullOrWhiteSpace(remote.Title);
|
||||
bool localHasMetadata = !string.IsNullOrWhiteSpace(songInfo.Title);
|
||||
bool remoteHasMetadata = !string.IsNullOrWhiteSpace(compareTitle);
|
||||
|
||||
if (localHasMetadata && remoteHasMetadata)
|
||||
{
|
||||
double titleScore = GetStringSimilarity(local.Title, remote.Title);
|
||||
double artistScore = GetArtistSimilarity(local.Artists, remote.Artists);
|
||||
double albumScore = GetStringSimilarity(local.Album, remote.Album);
|
||||
double durationScore = GetDurationSimilarity(local.DurationMs, remote.Duration);
|
||||
double titleScore = GetStringSimilarity(songInfo.Title, compareTitle);
|
||||
double artistScore = GetStringSimilarity(songInfo.Artist, compareArtist);
|
||||
double albumScore = GetStringSimilarity(songInfo.Album, compareAlbum);
|
||||
double durationScore = GetDurationSimilarity(songInfo.DurationMs, compareDurationMs);
|
||||
|
||||
totalScore = (titleScore * WeightTitle) +
|
||||
(artistScore * WeightArtist) +
|
||||
@@ -41,12 +52,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
else
|
||||
{
|
||||
string? localQuery = localHasMetadata
|
||||
? $"{local.Title} {string.Join(" ", local.Artists ?? [])}"
|
||||
: Path.GetFileNameWithoutExtension(local.LinkedFileName);
|
||||
? $"{songInfo.Title} {songInfo.Artist}"
|
||||
: Path.GetFileNameWithoutExtension(songInfo.LinkedFileName);
|
||||
|
||||
string remoteQuery = remoteHasMetadata
|
||||
? $"{remote.Title} {string.Join(" ", remote.Artists ?? [])}"
|
||||
: Path.GetFileNameWithoutExtension(remote.Reference);
|
||||
string? remoteQuery = remoteHasMetadata
|
||||
? $"{compareTitle} {compareArtist}"
|
||||
: Path.GetFileNameWithoutExtension(compareFileName);
|
||||
|
||||
string fp1 = CreateSortedFingerprint(localQuery);
|
||||
string fp2 = CreateSortedFingerprint(remoteQuery);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ColorThiefDotNet;
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Impressionist.Abstractions;
|
||||
using Impressionist.Implementations;
|
||||
using System;
|
||||
@@ -50,7 +51,29 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return paletteResult;
|
||||
}
|
||||
|
||||
public static async Task<Dictionary<Vector3, int>> GetPixelColor(BitmapDecoder bitmapDecoder)
|
||||
public static List<Windows.UI.Color> GenerateChartColors(Windows.UI.Color baseColor, int count)
|
||||
{
|
||||
List<Windows.UI.Color> results = [];
|
||||
|
||||
var baseHsl = baseColor.ToHsl();
|
||||
double baseHue = baseHsl.H;
|
||||
double baseSaturation = baseHsl.S;
|
||||
double baseBrightness = baseHsl.L;
|
||||
|
||||
double step = 360.0 / count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
double newHue = (baseHue + (step * i)) % 360;
|
||||
|
||||
Windows.UI.Color newColor = CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(newHue, baseSaturation, baseBrightness);
|
||||
results.Add(newColor);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static async Task<Dictionary<Vector3, int>> GetPixelColor(BitmapDecoder bitmapDecoder)
|
||||
{
|
||||
var pixelDataProvider = await bitmapDecoder.GetPixelDataAsync();
|
||||
var pixels = pixelDataProvider.DetachPixelData();
|
||||
|
||||
@@ -40,16 +40,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
public static string LyricsCacheDirectory => Path.Combine(CacheFolder, "lyrics");
|
||||
public static string LrcLibLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "lrclib");
|
||||
public static string NeteaseLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "netease");
|
||||
public static string QQLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "qq");
|
||||
public static string KugouLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "kugou");
|
||||
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "amll-ttml-db");
|
||||
public static string AppleMusicCacheDirectory => Path.Combine(LyricsCacheDirectory, "apple-music");
|
||||
public static string LocalMusicCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-music");
|
||||
public static string LocalLrcCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-lrc");
|
||||
public static string LocalEslrcCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-eslrc");
|
||||
public static string LocalTtmlCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-ttml");
|
||||
public static string AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.jsonl");
|
||||
public static string AmllTtmlDbLastUpdatedPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-last-updated.txt");
|
||||
|
||||
@@ -60,26 +50,17 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static string PlayQueuePath => Path.Combine(LocalFolder, "play-queue.m3u");
|
||||
public static string PlayHistoryPath => Path.Combine(LocalFolder, "play-history.db");
|
||||
public static string FilesIndexPath => Path.Combine(LocalFolder, "files-index.db");
|
||||
public static string SongSearchMapPath => Path.Combine(LocalFolder, "song-search-map.db");
|
||||
public static string LyricsCachePath => Path.Combine(LyricsCacheDirectory, "lyrics-cache.db");
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(SettingsDirectory);
|
||||
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
|
||||
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(QQLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(KugouLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AppleMusicCacheDirectory);
|
||||
Directory.CreateDirectory(LocalMusicCacheDirectory);
|
||||
Directory.CreateDirectory(LocalLrcCacheDirectory);
|
||||
Directory.CreateDirectory(LocalEslrcCacheDirectory);
|
||||
Directory.CreateDirectory(LocalTtmlCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(LyricsCacheDirectory);
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
Directory.CreateDirectory(LocalAlbumArtCacheDirectory);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -42,19 +42,23 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return file;
|
||||
}
|
||||
|
||||
public static async Task<StorageFile?> PickSaveFileAsync<T>(IDictionary<string, IList<string>> fileTypeChoices)
|
||||
public static async Task<StorageFile?> PickSaveFileAsync<T>(IDictionary<string, IList<string>> fileTypeChoices, string? suggestedFileName = null)
|
||||
{
|
||||
var window = WindowHook.GetWindow<T>();
|
||||
|
||||
return await PickSaveFileAsync(window, fileTypeChoices);
|
||||
return await PickSaveFileAsync(window, fileTypeChoices, suggestedFileName);
|
||||
}
|
||||
|
||||
public static async Task<StorageFile?> PickSaveFileAsync<T>(T? window, IDictionary<string, IList<string>> fileTypeChoices)
|
||||
public static async Task<StorageFile?> PickSaveFileAsync<T>(T? window, IDictionary<string, IList<string>> fileTypeChoices, string? suggestedFileName = null)
|
||||
{
|
||||
if (window == null) return null;
|
||||
|
||||
var picker = new Windows.Storage.Pickers.FileSavePicker();
|
||||
picker.FileTypeChoices.AddRange(fileTypeChoices);
|
||||
if (suggestedFileName != null)
|
||||
{
|
||||
picker.SuggestedFileName = suggestedFileName;
|
||||
}
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(window);
|
||||
InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
@@ -1,153 +1,252 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ValueTransition<T>
|
||||
where T : struct
|
||||
public class ValueTransition<T> where T : struct
|
||||
{
|
||||
// 状态变量
|
||||
private T _currentValue;
|
||||
private double _durationSeconds;
|
||||
private double _delaySeconds;
|
||||
private double _delayRemaining;
|
||||
private EasingType? _easingType;
|
||||
private Func<T, T, double, T> _interpolator;
|
||||
private bool _isTransitioning;
|
||||
private double _progress;
|
||||
private T _startValue;
|
||||
private T _targetValue;
|
||||
|
||||
public double DurationSeconds => _durationSeconds;
|
||||
public double DelaySeconds => _delaySeconds;
|
||||
// 核心队列
|
||||
private readonly Queue<Keyframe<T>> _keyframeQueue = new Queue<Keyframe<T>>();
|
||||
|
||||
public bool IsTransitioning => _isTransitioning;
|
||||
// 时间控制
|
||||
private double _stepDuration; // 当前这一段的时长 (动态变化)
|
||||
private double _totalDurationForAutoSplit; // 自动均分模式的总时长
|
||||
private double _configuredDelaySeconds; // 配置的延迟时长
|
||||
|
||||
// 动画状态
|
||||
private Enums.EasingType? _easingType;
|
||||
private Func<T, T, double, T> _interpolator;
|
||||
private bool _isTransitioning;
|
||||
private double _progress; // 当前段的进度 (0.0 ~ 1.0)
|
||||
|
||||
// 公开属性
|
||||
public T Value => _currentValue;
|
||||
public T StartValue => _startValue;
|
||||
public T TargetValue => _targetValue;
|
||||
public EasingType? EasingType => _easingType;
|
||||
public double Progress => _progress;
|
||||
public bool IsTransitioning => _isTransitioning;
|
||||
public T TargetValue => _targetValue; // 获取当前段的目标值
|
||||
public Enums.EasingType? EasingType => _easingType;
|
||||
public double DurationSeconds => _totalDurationForAutoSplit;
|
||||
|
||||
public ValueTransition(T initialValue, double durationSeconds, Func<T, T, double, T>? interpolator = null, EasingType? easingType = null, double delaySeconds = 0)
|
||||
public ValueTransition(T initialValue, double defaultTotalDuration = 0.3, EasingType? defaultEasingType = null, Func<T, T, double, T>? interpolator = null)
|
||||
{
|
||||
_currentValue = initialValue;
|
||||
_startValue = initialValue;
|
||||
_targetValue = initialValue;
|
||||
_durationSeconds = durationSeconds;
|
||||
_delaySeconds = delaySeconds;
|
||||
_delayRemaining = 0;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
_totalDurationForAutoSplit = defaultTotalDuration;
|
||||
|
||||
if (interpolator == null)
|
||||
{
|
||||
// 默认缓动
|
||||
SetEasingType(Enums.EasingType.EaseInOutQuad);
|
||||
}
|
||||
else
|
||||
{
|
||||
_easingType = null;
|
||||
_interpolator = interpolator;
|
||||
}
|
||||
|
||||
if (interpolator != null)
|
||||
{
|
||||
_interpolator = interpolator;
|
||||
_easingType = null;
|
||||
}
|
||||
else if (easingType.HasValue)
|
||||
else if (defaultEasingType != null)
|
||||
{
|
||||
_easingType = easingType;
|
||||
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
|
||||
SetEasingType(defaultEasingType);
|
||||
}
|
||||
else
|
||||
{
|
||||
_easingType = Enums.EasingType.EaseInOutQuad;
|
||||
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
|
||||
SetEasingType(Enums.EasingType.EaseInOutQuad);
|
||||
}
|
||||
}
|
||||
|
||||
#region Configuration
|
||||
|
||||
public void SetDuration(double seconds)
|
||||
{
|
||||
if (seconds < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
|
||||
_durationSeconds = seconds;
|
||||
if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds));
|
||||
_totalDurationForAutoSplit = seconds;
|
||||
}
|
||||
|
||||
public void SetDurationMs(double millionSeconds) => SetDuration(millionSeconds / 1000.0);
|
||||
|
||||
/// <summary>
|
||||
/// 设置启动延迟。
|
||||
/// 原理:在动画队列最前方插入一个“数值不变”的关键帧。
|
||||
/// </summary>
|
||||
public void SetDelay(double seconds)
|
||||
{
|
||||
_delaySeconds = seconds;
|
||||
_configuredDelaySeconds = seconds;
|
||||
}
|
||||
|
||||
private void JumpTo(T value)
|
||||
public void SetEasingType(Enums.EasingType? easingType)
|
||||
{
|
||||
_easingType = easingType;
|
||||
_interpolator = GetInterpolatorByEasingType(easingType);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Control Methods
|
||||
|
||||
/// <summary>
|
||||
/// 立即跳转到指定值(停止动画)
|
||||
/// </summary>
|
||||
public void JumpTo(T value)
|
||||
{
|
||||
_keyframeQueue.Clear();
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 1f;
|
||||
_delayRemaining = 0;
|
||||
_isTransitioning = false;
|
||||
_progress = 0;
|
||||
}
|
||||
|
||||
public void Reset(T value)
|
||||
/// <summary>
|
||||
/// 模式 A: 精确控制模式
|
||||
/// 显式指定每一段的目标值和时长。
|
||||
/// </summary>
|
||||
public void Start(params Keyframe<T>[] keyframes)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 0f;
|
||||
_delayRemaining = 0;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
if (keyframes == null || keyframes.Length == 0) return;
|
||||
|
||||
public void StartTransition(T targetValue, bool jumpTo = false)
|
||||
{
|
||||
if (jumpTo)
|
||||
PrepareStart();
|
||||
|
||||
// 1. 处理延迟 (插入静止帧)
|
||||
if (_configuredDelaySeconds > 0)
|
||||
{
|
||||
JumpTo(targetValue);
|
||||
return;
|
||||
_keyframeQueue.Enqueue(new Keyframe<T>(_currentValue, _configuredDelaySeconds));
|
||||
}
|
||||
|
||||
if (!targetValue.Equals(_currentValue))
|
||||
// 2. 入队用户帧
|
||||
foreach (var kf in keyframes)
|
||||
{
|
||||
_startValue = _currentValue;
|
||||
_targetValue = targetValue;
|
||||
_progress = 0f;
|
||||
_delayRemaining = _delaySeconds;
|
||||
_isTransitioning = true;
|
||||
_keyframeQueue.Enqueue(kf);
|
||||
}
|
||||
|
||||
MoveToNextSegment(firstStart: true);
|
||||
}
|
||||
|
||||
public static bool Equals(double x, double y, double tolerance)
|
||||
/// <summary>
|
||||
/// 模式 B: 自动均分模式 (兼容旧写法)
|
||||
/// 指定一串目标值,系统根据 SetDuration 的总时长平均分配。
|
||||
/// </summary>
|
||||
public void Start(params T[] values)
|
||||
{
|
||||
var diff = Math.Abs(x - y);
|
||||
return diff <= tolerance || diff <= Math.Max(Math.Abs(x), Math.Abs(y)) * tolerance;
|
||||
if (values == null || values.Length == 0) return;
|
||||
|
||||
// 如果目标就是当前值且只有1帧,直接跳过以省性能
|
||||
if (values.Length == 1 && values[0].Equals(_currentValue) && _configuredDelaySeconds <= 0) return;
|
||||
|
||||
PrepareStart();
|
||||
|
||||
// 1. 处理延迟
|
||||
if (_configuredDelaySeconds > 0)
|
||||
{
|
||||
_keyframeQueue.Enqueue(new Keyframe<T>(_currentValue, _configuredDelaySeconds));
|
||||
}
|
||||
|
||||
// 2. 计算均分时长
|
||||
double autoStepDuration = _totalDurationForAutoSplit / values.Length;
|
||||
|
||||
// 3. 入队生成帧
|
||||
foreach (var val in values)
|
||||
{
|
||||
_keyframeQueue.Enqueue(new Keyframe<T>(val, autoStepDuration));
|
||||
}
|
||||
|
||||
MoveToNextSegment(firstStart: true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Core Logic
|
||||
|
||||
private void PrepareStart()
|
||||
{
|
||||
_keyframeQueue.Clear();
|
||||
_isTransitioning = true;
|
||||
}
|
||||
|
||||
private void MoveToNextSegment(bool firstStart = false)
|
||||
{
|
||||
if (_keyframeQueue.Count > 0)
|
||||
{
|
||||
var kf = _keyframeQueue.Dequeue();
|
||||
|
||||
// 起点逻辑:如果是刚开始,起点是当前值;如果是中间切换,起点是上一段的终点
|
||||
_startValue = firstStart ? _currentValue : _targetValue;
|
||||
_targetValue = kf.Value;
|
||||
_stepDuration = kf.Duration;
|
||||
|
||||
if (firstStart) _progress = 0f;
|
||||
// 注意:非 firstStart 时不重置 _progress,保留溢出值以平滑过渡
|
||||
}
|
||||
else
|
||||
{
|
||||
// 队列耗尽,动画结束
|
||||
_currentValue = _targetValue;
|
||||
_isTransitioning = false;
|
||||
_progress = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(TimeSpan elapsedTime)
|
||||
{
|
||||
if (!_isTransitioning) return;
|
||||
|
||||
if (_delayRemaining > 0)
|
||||
{
|
||||
double consume = Math.Min(_delayRemaining, elapsedTime.TotalSeconds);
|
||||
_delayRemaining -= consume;
|
||||
if (_delayRemaining > 0)
|
||||
return;
|
||||
elapsedTime = TimeSpan.FromSeconds(elapsedTime.TotalSeconds - consume);
|
||||
}
|
||||
double timeStep = elapsedTime.TotalSeconds;
|
||||
|
||||
if (_durationSeconds <= 0)
|
||||
// 使用 while 处理单帧时间过长跨越多段的情况
|
||||
while (timeStep > 0 && _isTransitioning)
|
||||
{
|
||||
_progress = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
_progress += elapsedTime.TotalSeconds / _durationSeconds;
|
||||
}
|
||||
// 计算当前帧的步进比例
|
||||
// 极小值保护,防止除以0
|
||||
double progressDelta = (_stepDuration > 0.000001) ? (timeStep / _stepDuration) : 1.0;
|
||||
|
||||
if (_progress >= 1f)
|
||||
{
|
||||
_progress = 1f;
|
||||
_currentValue = _targetValue;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentValue = _interpolator(_startValue, _targetValue, _progress);
|
||||
if (_progress + progressDelta >= 1.0)
|
||||
{
|
||||
// === 当前段结束 ===
|
||||
|
||||
// 1. 计算这一段实际消耗的时间
|
||||
double timeConsumed = (1.0 - _progress) * _stepDuration;
|
||||
|
||||
// 2. 剩余时间留给下一段
|
||||
timeStep -= timeConsumed;
|
||||
|
||||
// 3. 修正当前值到目标值
|
||||
_progress = 1.0;
|
||||
_currentValue = _targetValue;
|
||||
|
||||
// 4. 切换到下一段
|
||||
MoveToNextSegment();
|
||||
|
||||
// 5. 如果还有下一段,进度归零
|
||||
if (_isTransitioning) _progress = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// === 当前段进行中 ===
|
||||
_progress += progressDelta;
|
||||
timeStep = 0; // 时间耗尽
|
||||
|
||||
// 插值计算
|
||||
_currentValue = _interpolator(_startValue, _targetValue, _progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Func<T, T, double, T> GetInterpolatorByEasingType(EasingType? type)
|
||||
#endregion
|
||||
|
||||
#region Interpolators
|
||||
|
||||
private Func<T, T, double, T> GetInterpolatorByEasingType(Enums.EasingType? type)
|
||||
{
|
||||
if (typeof(T) == typeof(double))
|
||||
{
|
||||
@@ -156,58 +255,32 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
double s = (double)(object)start;
|
||||
double e = (double)(object)end;
|
||||
double t = progress;
|
||||
|
||||
// 使用 EasingHelper (假设您的项目中已有此辅助类)
|
||||
switch (type)
|
||||
{
|
||||
case Enums.EasingType.EaseInOutSine:
|
||||
t = EasingHelper.EaseInOutSine(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutQuad:
|
||||
t = EasingHelper.EaseInOutQuad(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutCubic:
|
||||
t = EasingHelper.EaseInOutCubic(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutQuart:
|
||||
t = EasingHelper.EaseInOutQuart(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutQuint:
|
||||
t = EasingHelper.EaseInOutQuint(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutExpo:
|
||||
t = EasingHelper.EaseInOutExpo(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutCirc:
|
||||
t = EasingHelper.EaseInOutCirc(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutBack:
|
||||
t = EasingHelper.EaseInOutBack(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutElastic:
|
||||
t = EasingHelper.EaseInOutElastic(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutBounce:
|
||||
t = EasingHelper.EaseInOutBounce(t);
|
||||
break;
|
||||
case Enums.EasingType.SmoothStep:
|
||||
t = EasingHelper.SmoothStep(t);
|
||||
break;
|
||||
case Enums.EasingType.Linear:
|
||||
t = EasingHelper.Linear(t);
|
||||
break;
|
||||
default:
|
||||
t = EasingHelper.EaseInOutQuad(t);
|
||||
break;
|
||||
case Enums.EasingType.EaseInOutSine: t = EasingHelper.EaseInOutSine(t); break;
|
||||
case Enums.EasingType.EaseInOutQuad: t = EasingHelper.EaseInOutQuad(t); break;
|
||||
case Enums.EasingType.EaseInOutCubic: t = EasingHelper.EaseInOutCubic(t); break;
|
||||
case Enums.EasingType.EaseInOutQuart: t = EasingHelper.EaseInOutQuart(t); break;
|
||||
case Enums.EasingType.EaseInOutQuint: t = EasingHelper.EaseInOutQuint(t); break;
|
||||
case Enums.EasingType.EaseInOutExpo: t = EasingHelper.EaseInOutExpo(t); break;
|
||||
case Enums.EasingType.EaseInOutCirc: t = EasingHelper.EaseInOutCirc(t); break;
|
||||
case Enums.EasingType.EaseInOutBack: t = EasingHelper.EaseInOutBack(t); break;
|
||||
case Enums.EasingType.EaseInOutElastic: t = EasingHelper.EaseInOutElastic(t); break;
|
||||
case Enums.EasingType.EaseInOutBounce: t = EasingHelper.EaseInOutBounce(t); break;
|
||||
case Enums.EasingType.SmoothStep: t = EasingHelper.SmoothStep(t); break;
|
||||
case Enums.EasingType.Linear: t = EasingHelper.Linear(t); break;
|
||||
default: t = EasingHelper.EaseInOutQuad(t); break;
|
||||
}
|
||||
|
||||
return (T)(object)(s + (e - s) * t);
|
||||
};
|
||||
}
|
||||
throw new NotSupportedException($"Easing type {type} is not supported for type {typeof(T)}.");
|
||||
|
||||
throw new NotSupportedException($"Type {typeof(T)} is not supported.");
|
||||
}
|
||||
|
||||
public void SetEasingType(EasingType? easingType)
|
||||
{
|
||||
_easingType = easingType;
|
||||
_interpolator = GetInterpolatorByEasingType(easingType);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace BetterLyrics.WinUI3.Hooks
|
||||
/// <param name="id"></param>
|
||||
/// <param name="keys"></param>
|
||||
/// <param name="action"></param>
|
||||
private static void RegisterHotKey(Window window, ShortcutID id, List<string> keys, Action action)
|
||||
private static void RegisterHotKey(Window window, ShortcutId id, List<string> keys, Action action)
|
||||
{
|
||||
if (keys.Count == 0) return;
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace BetterLyrics.WinUI3.Hooks
|
||||
}
|
||||
}
|
||||
|
||||
private static void UnregisterHotKey(Window window, ShortcutID id)
|
||||
private static void UnregisterHotKey(Window window, ShortcutId id)
|
||||
{
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
User32.UnregisterHotKey(hwnd, (int)id);
|
||||
@@ -66,13 +66,13 @@ namespace BetterLyrics.WinUI3.Hooks
|
||||
_keys.Remove((int)id);
|
||||
}
|
||||
|
||||
public static void UpdateHotKey(Window window, ShortcutID id, List<string> keys, Action action)
|
||||
public static void UpdateHotKey(Window window, ShortcutId id, List<string> keys, Action action)
|
||||
{
|
||||
UnregisterHotKey(window, id);
|
||||
RegisterHotKey(window, id, keys, action);
|
||||
}
|
||||
|
||||
public static bool IsHotKeyRegistered(ShortcutID id)
|
||||
public static bool IsHotKeyRegistered(ShortcutId id)
|
||||
{
|
||||
return _actions.ContainsKey((int)id);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ namespace BetterLyrics.WinUI3.Hooks
|
||||
return _keys.ContainsValue(keys);
|
||||
}
|
||||
|
||||
public static bool TryInvokeAction(ShortcutID id)
|
||||
public static bool TryInvokeAction(ShortcutId id)
|
||||
{
|
||||
return TryInvokeAction((int)id);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using FlaUI.Core.EventHandlers;
|
||||
using FlaUI.UIA3;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Threading;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models.Lyrics;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using DevWinUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -17,7 +19,7 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
IList<RenderLyricsLine>? lines,
|
||||
int startIndex,
|
||||
int endIndex,
|
||||
int playingLineIndex,
|
||||
int primaryPlayingLineIndex,
|
||||
double canvasHeight,
|
||||
double targetYScrollOffset,
|
||||
double playingLineTopOffsetFactor,
|
||||
@@ -29,13 +31,14 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
TimeSpan elapsedTime,
|
||||
bool isMouseScrolling,
|
||||
bool isLayoutChanged,
|
||||
bool isPlayingLineChanged,
|
||||
bool isMouseScrollingChanged
|
||||
bool isPrimaryPlayingLineChanged,
|
||||
bool isMouseScrollingChanged,
|
||||
double currentPositionMs
|
||||
)
|
||||
{
|
||||
if (lines == null) return;
|
||||
|
||||
var currentPlayingLine = lines.ElementAtOrDefault(playingLineIndex);
|
||||
var currentPlayingLine = lines.ElementAtOrDefault(primaryPlayingLineIndex);
|
||||
if (currentPlayingLine == null) return;
|
||||
|
||||
var phoneticOpacity = lyricsStyle.PhoneticLyricsOpacity / 100.0;
|
||||
@@ -47,13 +50,17 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
var line = lines.ElementAtOrDefault(i);
|
||||
if (line == null) continue;
|
||||
|
||||
if (isLayoutChanged || isPlayingLineChanged || isMouseScrollingChanged)
|
||||
{
|
||||
int lineCountDelta = i - playingLineIndex;
|
||||
int absLineCountDelta = Math.Abs(lineCountDelta);
|
||||
double distanceFromPlayingLine = Math.Abs(line.OriginalPosition.Y - currentPlayingLine.OriginalPosition.Y);
|
||||
bool isSecondaryLinePlaying = line.GetIsPlaying(currentPositionMs);
|
||||
bool isSecondaryLinePlayingChanged = line.IsPlayingLastFrame != isSecondaryLinePlaying;
|
||||
line.IsPlayingLastFrame = isSecondaryLinePlaying;
|
||||
|
||||
double distanceFactor = 0;
|
||||
// 行动画
|
||||
if (isLayoutChanged || isPrimaryPlayingLineChanged || isMouseScrollingChanged || isSecondaryLinePlayingChanged)
|
||||
{
|
||||
int lineCountDelta = i - primaryPlayingLineIndex;
|
||||
double distanceFromPlayingLine = Math.Abs(line.PrimaryPosition.Y - currentPlayingLine.PrimaryPosition.Y);
|
||||
|
||||
double distanceFactor;
|
||||
if (lineCountDelta < 0)
|
||||
{
|
||||
distanceFactor = Math.Clamp(distanceFromPlayingLine / (canvasHeight * playingLineTopOffsetFactor), 0, 1);
|
||||
@@ -88,45 +95,53 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
|
||||
line.BlurAmountTransition.SetDuration(yScrollDuration);
|
||||
line.BlurAmountTransition.SetDelay(yScrollDelay);
|
||||
line.BlurAmountTransition.StartTransition(isMouseScrolling ? 0 : (lyricsEffect.IsLyricsBlurEffectEnabled ? (5 * distanceFactor) : 0));
|
||||
line.BlurAmountTransition.Start(
|
||||
(isMouseScrolling || isSecondaryLinePlaying) ? 0 :
|
||||
(lyricsEffect.IsLyricsBlurEffectEnabled ? (5 * distanceFactor) : 0));
|
||||
|
||||
line.ScaleTransition.SetDuration(yScrollDuration);
|
||||
line.ScaleTransition.SetDelay(yScrollDelay);
|
||||
line.ScaleTransition.StartTransition(
|
||||
lyricsEffect.IsLyricsOutOfSightEffectEnabled ?
|
||||
line.ScaleTransition.Start(
|
||||
isSecondaryLinePlaying ? _highlightedScale :
|
||||
(lyricsEffect.IsLyricsOutOfSightEffectEnabled ?
|
||||
(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale)) :
|
||||
_highlightedScale);
|
||||
_highlightedScale));
|
||||
|
||||
line.PhoneticOpacityTransition.SetDuration(yScrollDuration);
|
||||
line.PhoneticOpacityTransition.SetDelay(yScrollDelay);
|
||||
line.PhoneticOpacityTransition.StartTransition(
|
||||
line.PhoneticOpacityTransition.Start(
|
||||
isSecondaryLinePlaying ? phoneticOpacity :
|
||||
CalculateTargetOpacity(phoneticOpacity, phoneticOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||
|
||||
// 原文不透明度(已播放)
|
||||
line.PlayedOriginalOpacityTransition.SetDuration(yScrollDuration);
|
||||
line.PlayedOriginalOpacityTransition.SetDelay(yScrollDelay);
|
||||
line.PlayedOriginalOpacityTransition.StartTransition(
|
||||
line.PlayedOriginalOpacityTransition.Start(
|
||||
isSecondaryLinePlaying ? 1.0 :
|
||||
CalculateTargetOpacity(originalOpacity, 1.0, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||
|
||||
// 原文不透明度(未播放)
|
||||
line.UnplayedOriginalOpacityTransition.SetDuration(yScrollDuration);
|
||||
line.UnplayedOriginalOpacityTransition.SetDelay(yScrollDelay);
|
||||
line.UnplayedOriginalOpacityTransition.StartTransition(
|
||||
line.UnplayedOriginalOpacityTransition.Start(
|
||||
isSecondaryLinePlaying ? originalOpacity :
|
||||
CalculateTargetOpacity(originalOpacity, originalOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||
|
||||
line.TranslatedOpacityTransition.SetDuration(yScrollDuration);
|
||||
line.TranslatedOpacityTransition.SetDelay(yScrollDelay);
|
||||
line.TranslatedOpacityTransition.StartTransition(
|
||||
line.TranslatedOpacityTransition.Start(
|
||||
isSecondaryLinePlaying ? translatedOpacity :
|
||||
CalculateTargetOpacity(translatedOpacity, translatedOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||
|
||||
line.ColorTransition.SetDuration(yScrollDuration);
|
||||
line.ColorTransition.SetDelay(yScrollDelay);
|
||||
line.ColorTransition.StartTransition(absLineCountDelta == 0 ? fgColor : bgColor);
|
||||
line.ColorTransition.Start(isSecondaryLinePlaying ? fgColor : bgColor);
|
||||
|
||||
line.AngleTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
||||
line.AngleTransition.SetDuration(yScrollDuration);
|
||||
line.AngleTransition.SetDelay(yScrollDelay);
|
||||
line.AngleTransition.StartTransition(
|
||||
line.AngleTransition.Start(
|
||||
(lyricsEffect.IsFanLyricsEnabled && !isMouseScrolling) ?
|
||||
Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > playingLineIndex ? 1 : -1) :
|
||||
Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > primaryPlayingLineIndex ? 1 : -1) :
|
||||
0);
|
||||
|
||||
line.YOffsetTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
||||
@@ -134,18 +149,134 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
line.YOffsetTransition.SetDelay(yScrollDelay);
|
||||
// 设计之初是当 isLayoutChanged 为真时 jumpTo
|
||||
// 但考虑到动画视觉,强制使用动画
|
||||
line.YOffsetTransition.StartTransition(targetYScrollOffset);
|
||||
line.YOffsetTransition.Start(targetYScrollOffset);
|
||||
}
|
||||
|
||||
line.AngleTransition.Update(elapsedTime);
|
||||
line.ScaleTransition.Update(elapsedTime);
|
||||
line.BlurAmountTransition.Update(elapsedTime);
|
||||
line.PhoneticOpacityTransition.Update(elapsedTime);
|
||||
line.PlayedOriginalOpacityTransition.Update(elapsedTime);
|
||||
line.UnplayedOriginalOpacityTransition.Update(elapsedTime);
|
||||
line.TranslatedOpacityTransition.Update(elapsedTime);
|
||||
line.YOffsetTransition.Update(elapsedTime);
|
||||
line.ColorTransition.Update(elapsedTime);
|
||||
var maxAnimationDurationMs = Math.Max(line.EndMs - currentPositionMs, 0);
|
||||
|
||||
// 字符动画
|
||||
foreach (var renderChar in line.PrimaryRenderChars)
|
||||
{
|
||||
renderChar.ProgressPlayed = renderChar.GetPlayProgress(currentPositionMs);
|
||||
|
||||
bool isCharPlaying = renderChar.GetIsPlaying(currentPositionMs);
|
||||
bool isCharPlayingChanged = renderChar.IsPlayingLastFrame != isCharPlaying;
|
||||
|
||||
if (isSecondaryLinePlayingChanged || isCharPlayingChanged)
|
||||
{
|
||||
if (lyricsEffect.IsLyricsGlowEffectEnabled)
|
||||
{
|
||||
double targetGlow = lyricsEffect.IsLyricsGlowEffectAmountAutoAdjust ? renderChar.LayoutRect.Height * 0.2 : lyricsEffect.LyricsGlowEffectAmount;
|
||||
switch (lyricsEffect.LyricsGlowEffectScope)
|
||||
{
|
||||
case Enums.LyricsEffectScope.LineStartToCurrentChar:
|
||||
if (isSecondaryLinePlayingChanged && isSecondaryLinePlaying)
|
||||
{
|
||||
var stepInOutDuration = Math.Min(Time.AnimationDuration.TotalMilliseconds, maxAnimationDurationMs) / 2.0 / 1000.0;
|
||||
var stepLastingDuration = Math.Max(maxAnimationDurationMs / 1000.0 - stepInOutDuration * 2, 0);
|
||||
renderChar.GlowTransition.Start(
|
||||
new Models.Keyframe<double>(targetGlow, stepInOutDuration),
|
||||
new Models.Keyframe<double>(targetGlow, stepLastingDuration),
|
||||
new Models.Keyframe<double>(0, stepInOutDuration)
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lyricsEffect.IsLyricsFloatAnimationEnabled)
|
||||
{
|
||||
double targetFloat =
|
||||
lyricsEffect.IsLyricsFloatAnimationAmountAutoAdjust ? renderChar.LayoutRect.Height * 0.1 : lyricsEffect.LyricsFloatAnimationAmount;
|
||||
|
||||
if (isSecondaryLinePlayingChanged)
|
||||
{
|
||||
renderChar.FloatTransition.Start(isSecondaryLinePlaying ? targetFloat : 0);
|
||||
}
|
||||
if (isCharPlayingChanged)
|
||||
{
|
||||
renderChar.FloatTransition.SetDurationMs(Math.Min(lyricsEffect.LyricsFloatAnimationDuration, maxAnimationDurationMs));
|
||||
renderChar.FloatTransition.Start(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCharPlayingChanged)
|
||||
{
|
||||
renderChar.IsPlayingLastFrame = isCharPlaying;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 音节动画
|
||||
foreach (var syllable in line.PrimaryRenderSyllables)
|
||||
{
|
||||
bool isSyllablePlaying = syllable.GetIsPlaying(currentPositionMs);
|
||||
bool isSyllablePlayingChanged = syllable.IsPlayingLastFrame != isSyllablePlaying;
|
||||
|
||||
if (isSyllablePlayingChanged)
|
||||
{
|
||||
var syllableHeight = syllable.ChildrenRenderLyricsChars.FirstOrDefault()?.LayoutRect.Height ?? 0;
|
||||
|
||||
if (lyricsEffect.IsLyricsScaleEffectEnabled)
|
||||
{
|
||||
double targetScale =
|
||||
lyricsEffect.IsLyricsScaleEffectAmountAutoAdjust ? 1.15 : lyricsEffect.LyricsScaleEffectAmount / 100.0;
|
||||
|
||||
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
|
||||
{
|
||||
if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration)
|
||||
{
|
||||
if (isSyllablePlaying)
|
||||
{
|
||||
var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0;
|
||||
renderChar.ScaleTransition.Start(
|
||||
new Models.Keyframe<double>(targetScale, stepDuration),
|
||||
new Models.Keyframe<double>(1.0, stepDuration)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lyricsEffect.IsLyricsGlowEffectEnabled)
|
||||
{
|
||||
double targetGlow = lyricsEffect.IsLyricsGlowEffectAmountAutoAdjust ? syllableHeight * 0.2 : lyricsEffect.LyricsGlowEffectAmount;
|
||||
switch (lyricsEffect.LyricsGlowEffectScope)
|
||||
{
|
||||
case Enums.LyricsEffectScope.LongDurationSyllable:
|
||||
if (syllable.DurationMs >= lyricsEffect.LyricsGlowEffectLongSyllableDuration)
|
||||
{
|
||||
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
|
||||
{
|
||||
if (isSyllablePlaying)
|
||||
{
|
||||
var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0;
|
||||
renderChar.GlowTransition.Start(
|
||||
new Models.Keyframe<double>(targetGlow, stepDuration),
|
||||
new Models.Keyframe<double>(0, stepDuration)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
syllable.IsPlayingLastFrame = isSyllablePlaying;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新动画
|
||||
foreach (var renderChar in line.PrimaryRenderChars)
|
||||
{
|
||||
renderChar.Update(elapsedTime);
|
||||
}
|
||||
|
||||
line.Update(elapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Lyrics;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using System;
|
||||
@@ -79,50 +79,52 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
// 左上角坐标
|
||||
line.TopLeftPosition = new Vector2(0, (float)currentY);
|
||||
// 注音层
|
||||
line.PhoneticPosition = line.TopLeftPosition;
|
||||
if (line.PhoneticCanvasTextLayout != null)
|
||||
line.TertiaryPosition = line.TopLeftPosition;
|
||||
if (line.TertiaryTextLayout != null)
|
||||
{
|
||||
currentY += line.PhoneticCanvasTextLayout.LayoutBounds.Height;
|
||||
currentY += line.TertiaryTextLayout.LayoutBounds.Height;
|
||||
// 间距
|
||||
currentY += (line.PhoneticCanvasTextLayout.LayoutBounds.Height / line.PhoneticCanvasTextLayout.LineCount) * 0.1;
|
||||
currentY += (line.TertiaryTextLayout.LayoutBounds.Height / line.TertiaryTextLayout.LineCount) * 0.1;
|
||||
|
||||
actualWidth = Math.Max(actualWidth, line.PhoneticCanvasTextLayout.LayoutBounds.Width);
|
||||
actualWidth = Math.Max(actualWidth, line.TertiaryTextLayout.LayoutBounds.Width);
|
||||
}
|
||||
|
||||
// 原文层
|
||||
line.OriginalPosition = new Vector2(0, (float)currentY);
|
||||
if (line.OriginalCanvasTextLayout != null)
|
||||
line.PrimaryPosition = new Vector2(0, (float)currentY);
|
||||
if (line.PrimaryTextLayout != null)
|
||||
{
|
||||
currentY += line.OriginalCanvasTextLayout.LayoutBounds.Height;
|
||||
currentY += line.PrimaryTextLayout.LayoutBounds.Height;
|
||||
|
||||
actualWidth = Math.Max(actualWidth, line.OriginalCanvasTextLayout.LayoutBounds.Width);
|
||||
actualWidth = Math.Max(actualWidth, line.PrimaryTextLayout.LayoutBounds.Width);
|
||||
}
|
||||
|
||||
// 翻译层
|
||||
if (line.TranslatedCanvasTextLayout != null)
|
||||
if (line.SecondaryTextLayout != null)
|
||||
{
|
||||
// 间距
|
||||
currentY += (line.TranslatedCanvasTextLayout.LayoutBounds.Height / line.TranslatedCanvasTextLayout.LineCount) * 0.1;
|
||||
currentY += (line.SecondaryTextLayout.LayoutBounds.Height / line.SecondaryTextLayout.LineCount) * 0.1;
|
||||
}
|
||||
line.TranslatedPosition = new Vector2(0, (float)currentY);
|
||||
if (line.TranslatedCanvasTextLayout != null)
|
||||
line.SecondaryPosition = new Vector2(0, (float)currentY);
|
||||
if (line.SecondaryTextLayout != null)
|
||||
{
|
||||
currentY += line.TranslatedCanvasTextLayout.LayoutBounds.Height;
|
||||
currentY += line.SecondaryTextLayout.LayoutBounds.Height;
|
||||
|
||||
actualWidth = Math.Max(actualWidth, line.TranslatedCanvasTextLayout.LayoutBounds.Width);
|
||||
actualWidth = Math.Max(actualWidth, line.SecondaryTextLayout.LayoutBounds.Width);
|
||||
}
|
||||
|
||||
// 右下角坐标
|
||||
line.BottomRightPosition = new Vector2(0 + (float)actualWidth, (float)currentY);
|
||||
|
||||
// 行间距
|
||||
if (line.OriginalCanvasTextLayout != null)
|
||||
if (line.PrimaryTextLayout != null)
|
||||
{
|
||||
currentY += (line.OriginalCanvasTextLayout.LayoutBounds.Height / line.OriginalCanvasTextLayout.LineCount) * style.LyricsLineSpacingFactor;
|
||||
currentY += (line.PrimaryTextLayout.LayoutBounds.Height / line.PrimaryTextLayout.LineCount) * style.LyricsLineSpacingFactor;
|
||||
}
|
||||
|
||||
// 更新中心点
|
||||
line.UpdateCenterPosition(lyricsWidth, style.LyricsAlignmentType);
|
||||
|
||||
line.RecreateRenderChars();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,9 +140,9 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
var currentLine = lines.ElementAtOrDefault(playingLineIndex);
|
||||
var firstLine = lines.FirstOrDefault();
|
||||
|
||||
if (currentLine?.OriginalCanvasTextLayout == null || firstLine == null) return null;
|
||||
if (currentLine?.PrimaryTextLayout == null || firstLine == null) return null;
|
||||
|
||||
return -currentLine.OriginalPosition.Y + firstLine.OriginalPosition.Y
|
||||
return -currentLine.PrimaryPosition.Y + firstLine.PrimaryPosition.Y
|
||||
- (currentLine.BottomRightPosition.Y - currentLine.TopLeftPosition.Y) / 2.0;
|
||||
}
|
||||
|
||||
@@ -187,6 +189,37 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
return lines.Last().BottomRightPosition.Y;
|
||||
}
|
||||
|
||||
public static void CalculateLanes(IList<RenderLyricsLine>? lines, int toleranceMs = 50)
|
||||
{
|
||||
if (lines == null) return;
|
||||
var lanesEndMs = new List<int> { 0 };
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var start = line.StartMs;
|
||||
var end = line.EndMs;
|
||||
|
||||
int assignedLane = -1;
|
||||
for (int i = 0; i < lanesEndMs.Count; i++)
|
||||
{
|
||||
if (lanesEndMs[i] <= start + toleranceMs)
|
||||
{
|
||||
assignedLane = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (assignedLane == -1)
|
||||
{
|
||||
assignedLane = lanesEndMs.Count;
|
||||
lanesEndMs.Add(0);
|
||||
}
|
||||
|
||||
lanesEndMs[assignedLane] = end;
|
||||
line.LaneIndex = assignedLane;
|
||||
}
|
||||
}
|
||||
|
||||
public static int FindMouseHoverLineIndex(
|
||||
IList<RenderLyricsLine>? lines,
|
||||
bool isMouseInLyricsArea,
|
||||
@@ -208,7 +241,7 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
var line = lines[mid];
|
||||
if (line.OriginalCanvasTextLayout == null) break;
|
||||
if (line.PrimaryTextLayout == null) break;
|
||||
double value = offset + line.BottomRightPosition.Y;
|
||||
if (value >= mousePosition.Y) { result = mid; right = mid - 1; }
|
||||
else { left = mid + 1; }
|
||||
@@ -234,7 +267,7 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
var line = lines[mid];
|
||||
if (line.OriginalCanvasTextLayout == null) break;
|
||||
if (line.PrimaryTextLayout == null) break;
|
||||
double value = offset + line.BottomRightPosition.Y;
|
||||
// 理论上说应该使用下面这一行来精确计算视野内的首个可见行,但是考虑到动画视觉效果,还是注释掉了
|
||||
//if (value >= lyricsY) { result = mid; right = mid - 1; }
|
||||
@@ -251,7 +284,7 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
var line = lines[mid];
|
||||
if (line.OriginalCanvasTextLayout == null) break;
|
||||
if (line.PrimaryTextLayout == null) break;
|
||||
double value = offset + line.BottomRightPosition.Y;
|
||||
// 同理
|
||||
//if (value >= lyricsY + lyricsHeight) { result = mid; right = mid - 1; }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Lyrics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -13,48 +15,66 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
_lastFoundIndex = 0;
|
||||
}
|
||||
|
||||
public int GetCurrentLineIndex(double currentTimeMs, LyricsData? lyricsData)
|
||||
public int GetCurrentLineIndex(double currentTimeMs, IList<RenderLyricsLine>? lines)
|
||||
{
|
||||
if (lyricsData == null || lyricsData.LyricsLines.Count == 0) return 0;
|
||||
var lines = lyricsData.LyricsLines;
|
||||
if (lines == null || lines.Count == 0) return 0;
|
||||
|
||||
// Cache hit
|
||||
if (IsTimeInLine(currentTimeMs, lines, _lastFoundIndex)) return _lastFoundIndex;
|
||||
if (_lastFoundIndex + 1 < lines.Count && IsTimeInLine(currentTimeMs, lines, _lastFoundIndex + 1))
|
||||
if (_lastFoundIndex >= 0 && _lastFoundIndex < lines.Count)
|
||||
{
|
||||
_lastFoundIndex++;
|
||||
return _lastFoundIndex;
|
||||
var lastLine = lines[_lastFoundIndex];
|
||||
if (lastLine.LaneIndex == 0 && IsTimeInLine(currentTimeMs, lines, _lastFoundIndex))
|
||||
{
|
||||
return _lastFoundIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss
|
||||
int bestCandidateIndex = -1;
|
||||
int bestCandidateLane = int.MaxValue;
|
||||
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
if (IsTimeInLine(currentTimeMs, lines, i))
|
||||
{
|
||||
_lastFoundIndex = i;
|
||||
return i;
|
||||
var currentLine = lines[i];
|
||||
int currentLane = currentLine.LaneIndex;
|
||||
|
||||
if (currentLane == 0)
|
||||
{
|
||||
_lastFoundIndex = i;
|
||||
return i;
|
||||
}
|
||||
|
||||
if (currentLane < bestCandidateLane)
|
||||
{
|
||||
bestCandidateIndex = i;
|
||||
bestCandidateLane = currentLane;
|
||||
}
|
||||
}
|
||||
else if (lines[i].StartMs > currentTimeMs + 1000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Default
|
||||
if (bestCandidateIndex != -1)
|
||||
{
|
||||
_lastFoundIndex = bestCandidateIndex;
|
||||
return bestCandidateIndex;
|
||||
}
|
||||
|
||||
return Math.Min(_lastFoundIndex, lines.Count - 1);
|
||||
}
|
||||
|
||||
public LinePlaybackState GetLinePlayingProgress(
|
||||
double currentTimeMs,
|
||||
LyricsLine line,
|
||||
LyricsLine? nextLine,
|
||||
double songDurationMs,
|
||||
bool isForceWordByWord)
|
||||
RenderLyricsLine line,
|
||||
WordByWordEffectMode wordByWordEffectMode)
|
||||
{
|
||||
var state = new LinePlaybackState { SyllableStartIndex = 0, SyllableLength = 0, SyllableProgress = 0 };
|
||||
|
||||
if (line == null) return state;
|
||||
|
||||
double lineEndMs;
|
||||
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
|
||||
else if (nextLine != null) lineEndMs = nextLine.StartMs;
|
||||
else lineEndMs = songDurationMs;
|
||||
double lineEndMs = line.EndMs;
|
||||
|
||||
// 还没到
|
||||
if (currentTimeMs < line.StartMs) return state;
|
||||
@@ -63,42 +83,54 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
if (currentTimeMs > lineEndMs)
|
||||
{
|
||||
state.SyllableProgress = 1f;
|
||||
state.SyllableStartIndex = Math.Max(0, line.OriginalText.Length - 1);
|
||||
state.SyllableStartIndex = Math.Max(0, line.PrimaryText.Length - 1);
|
||||
state.SyllableLength = 1;
|
||||
return state;
|
||||
}
|
||||
|
||||
// 逐字
|
||||
if (line.LyricsSyllables != null && line.LyricsSyllables.Count > 1)
|
||||
switch (wordByWordEffectMode)
|
||||
{
|
||||
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
|
||||
}
|
||||
|
||||
// 强制逐字
|
||||
if (isForceWordByWord && line.OriginalText.Length > 0)
|
||||
{
|
||||
return CalculateSimulatedProgress(currentTimeMs, line, lineEndMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 普通行
|
||||
state.SyllableStartIndex = line.OriginalText.Length;
|
||||
state.SyllableProgress = 1f;
|
||||
return state;
|
||||
case WordByWordEffectMode.Auto:
|
||||
if (line.PrimaryRenderSyllables.Count > 1)
|
||||
{
|
||||
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.SyllableStartIndex = line.PrimaryText.Length;
|
||||
state.SyllableProgress = 1f;
|
||||
return state;
|
||||
}
|
||||
case WordByWordEffectMode.Never:
|
||||
state.SyllableStartIndex = line.PrimaryText.Length;
|
||||
state.SyllableProgress = 1f;
|
||||
return state;
|
||||
case WordByWordEffectMode.Always:
|
||||
if (line.PrimaryRenderSyllables.Count > 1)
|
||||
{
|
||||
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CalculateSimulatedProgress(currentTimeMs, line, lineEndMs);
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
private LinePlaybackState CalculateSyllableProgress(double time, LyricsLine line, double lineEndMs)
|
||||
private LinePlaybackState CalculateSyllableProgress(double time, RenderLyricsLine line, double lineEndMs)
|
||||
{
|
||||
var state = new LinePlaybackState();
|
||||
int count = line.LyricsSyllables.Count;
|
||||
int count = line.PrimaryRenderSyllables.Count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var timing = line.LyricsSyllables[i];
|
||||
var nextTiming = (i + 1 < count) ? line.LyricsSyllables[i + 1] : null;
|
||||
var timing = line.PrimaryRenderSyllables[i];
|
||||
var nextTiming = (i + 1 < count) ? line.PrimaryRenderSyllables[i + 1] : null;
|
||||
|
||||
double timingEndMs = timing.EndMs ?? nextTiming?.StartMs ?? lineEndMs;
|
||||
//double timingEndMs = timing.EndMs ?? nextTiming?.StartMs ?? lineEndMs;
|
||||
double timingEndMs = timing.EndMs;
|
||||
|
||||
// 在当前字范围内
|
||||
if (time >= timing.StartMs && time <= timingEndMs)
|
||||
@@ -122,10 +154,10 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
return state;
|
||||
}
|
||||
|
||||
private LinePlaybackState CalculateSimulatedProgress(double time, LyricsLine line, double lineEndMs)
|
||||
private LinePlaybackState CalculateSimulatedProgress(double time, RenderLyricsLine line, double lineEndMs)
|
||||
{
|
||||
var state = new LinePlaybackState();
|
||||
int textLength = line.OriginalText.Length;
|
||||
int textLength = line.PrimaryText.Length;
|
||||
|
||||
double progress = (time - line.StartMs) / (lineEndMs - line.StartMs);
|
||||
progress = Math.Clamp(progress, 0, 1);
|
||||
@@ -140,7 +172,7 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
return state;
|
||||
}
|
||||
|
||||
private bool IsTimeInLine(double time, IList<LyricsLine> lines, int index)
|
||||
private bool IsTimeInLine(double time, IList<RenderLyricsLine> lines, int index)
|
||||
{
|
||||
if (index < 0 || index >= lines.Count) return false;
|
||||
var line = lines[index];
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Db
|
||||
{
|
||||
public partial class FilesIndexDbContext : DbContext
|
||||
{
|
||||
public FilesIndexDbContext(DbContextOptions<FilesIndexDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<FilesIndexItem> FilesIndex { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Db
|
||||
{
|
||||
public partial class PlayHistoryDbContext : DbContext
|
||||
{
|
||||
public PlayHistoryDbContext(DbContextOptions<PlayHistoryDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<PlayHistoryItem> PlayHistory { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using BetterLyrics.WinUI3.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.DbContext
|
||||
{
|
||||
public partial class FilesIndexDbContext : Microsoft.EntityFrameworkCore.DbContext
|
||||
{
|
||||
public FilesIndexDbContext(DbContextOptions<FilesIndexDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<FilesIndexItem> FilesIndex { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.DbContext
|
||||
{
|
||||
public partial class LyricsCacheDbContext : Microsoft.EntityFrameworkCore.DbContext
|
||||
{
|
||||
public LyricsCacheDbContext(DbContextOptions<LyricsCacheDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<LyricsCacheItem> LyricsCache { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using BetterLyrics.WinUI3.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.DbContext
|
||||
{
|
||||
public partial class PlayHistoryDbContext : Microsoft.EntityFrameworkCore.DbContext
|
||||
{
|
||||
public PlayHistoryDbContext(DbContextOptions<PlayHistoryDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<PlayHistoryItem> PlayHistory { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.DbContext
|
||||
{
|
||||
public partial class SongSearchMapDbContext : Microsoft.EntityFrameworkCore.DbContext
|
||||
{
|
||||
public DbSet<MappedSongSearchQuery> SongSearchMap { get; set; }
|
||||
|
||||
public SongSearchMapDbContext(DbContextOptions<SongSearchMapDbContext> options) : base(options) { }
|
||||
}
|
||||
}
|
||||
@@ -3,32 +3,26 @@ using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Entities
|
||||
{
|
||||
[Index(nameof(MediaFolderId))] // 普通索引
|
||||
[Index(nameof(ParentUri))] // 普通索引
|
||||
[Index(nameof(Uri), IsUnique = true)] // 唯一索引
|
||||
public class FilesIndexItem
|
||||
{
|
||||
[Key] // 主键
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // 明确指定为自增 (Identity)
|
||||
public int Id { get; set; }
|
||||
[Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
|
||||
|
||||
// 关联到 MediaFolder.Id
|
||||
// 注意:作为索引列,必须限制长度,否则 SQL Server 会报错 (索引最大900字节)
|
||||
[MaxLength(450)]
|
||||
public string MediaFolderId { get; set; }
|
||||
[MaxLength(450)] public string MediaFolderId { get; set; }
|
||||
|
||||
// 存储父文件夹的标准 URI
|
||||
// 允许为空
|
||||
[MaxLength(450)]
|
||||
public string? ParentUri { get; set; }
|
||||
[MaxLength(450)] public string? ParentUri { get; set; }
|
||||
|
||||
// 唯一索引列
|
||||
// 必须限制长度。450字符 * 2字节/字符 = 900字节 (正好卡在 SQL Server 限制内)
|
||||
[Required]
|
||||
[MaxLength(450)]
|
||||
public string Uri { get; set; }
|
||||
[Required][MaxLength(450)] public string Uri { get; set; }
|
||||
|
||||
public string FileName { get; set; } = "";
|
||||
|
||||
@@ -41,7 +35,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
// 下面的元数据字段通常不需要索引,可以使用 MaxLength 稍微优化空间,
|
||||
// 或者直接留空(默认为 nvarchar(max))
|
||||
public string Title { get; set; } = "";
|
||||
public string Artists { get; set; } = "";
|
||||
[Column("Artists")] public string Artist { get; set; } = "";
|
||||
public string Album { get; set; } = "";
|
||||
public int? Year { get; set; }
|
||||
public int Bitrate { get; set; }
|
||||
@@ -49,11 +43,9 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public int BitDepth { get; set; }
|
||||
public int Duration { get; set; }
|
||||
|
||||
[MaxLength(50)] // 格式名称通常很短,限制一下是个好习惯
|
||||
public string AudioFormatName { get; set; } = "";
|
||||
[MaxLength(50)] public string AudioFormatName { get; set; } = "";
|
||||
|
||||
[MaxLength(20)]
|
||||
public string AudioFormatShortName { get; set; } = "";
|
||||
[MaxLength(20)] public string AudioFormatShortName { get; set; } = "";
|
||||
|
||||
public string Encoder { get; set; } = "";
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using NTextCat.Commons;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class LyricsSearchResult : ObservableObject, ICloneable
|
||||
[Table("LyricsCache")]
|
||||
// 建立联合索引,确保同一个 Provider 下,同一个 Hash 只有一条记录
|
||||
[Index(nameof(CacheKey), nameof(Provider), IsUnique = false)]
|
||||
public partial class LyricsCacheItem : ObservableObject, ICloneable
|
||||
{
|
||||
[Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
|
||||
|
||||
[MaxLength(64)][Required] public string CacheKey { get; set; }
|
||||
|
||||
public LyricsSearchProvider Provider { get; set; }
|
||||
[ObservableProperty] public partial TranslationSearchProvider? TranslationProvider { get; set; }
|
||||
[ObservableProperty] public partial TransliterationSearchProvider? TransliterationProvider { get; set; }
|
||||
@@ -25,33 +33,38 @@ namespace BetterLyrics.WinUI3.Models
|
||||
/// </summary>
|
||||
public string? Transliteration { get; set; }
|
||||
|
||||
[MaxLength(255)]
|
||||
public string? Title { get; set; }
|
||||
public string[]? Artists { get; set; }
|
||||
[MaxLength(255)]
|
||||
public string? Artist { get; set; }
|
||||
[MaxLength(255)]
|
||||
public string? Album { get; set; }
|
||||
public double? Duration { get; set; }
|
||||
[ObservableProperty] public partial int MatchPercentage { get; set; } = -1;
|
||||
[ObservableProperty] public partial string Reference { get; set; } = "about:blank";
|
||||
|
||||
public string? SelfPath { get; set; }
|
||||
[NotMapped][JsonIgnore] public bool IsFound => !string.IsNullOrEmpty(Raw);
|
||||
|
||||
[JsonIgnore] public bool IsFound => !string.IsNullOrEmpty(Raw);
|
||||
|
||||
[JsonIgnore] public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;
|
||||
|
||||
[JsonIgnore] public string? DisplayArtists => Artists?.Join("; ");
|
||||
[NotMapped][JsonIgnore] public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new LyricsSearchResult()
|
||||
return new LyricsCacheItem()
|
||||
{
|
||||
Album = this.Album,
|
||||
Duration = this.Duration,
|
||||
Provider = this.Provider,
|
||||
TranslationProvider = this.TranslationProvider,
|
||||
TransliterationProvider = this.TransliterationProvider,
|
||||
|
||||
Raw = this.Raw,
|
||||
Translation = this.Translation,
|
||||
Transliteration = this.Transliteration,
|
||||
|
||||
Title = this.Title,
|
||||
Artists = this.Artists,
|
||||
Artist = this.Artist,
|
||||
Album = this.Album,
|
||||
Duration = this.Duration,
|
||||
|
||||
MatchPercentage = this.MatchPercentage,
|
||||
Provider = this.Provider,
|
||||
Reference = this.Reference
|
||||
};
|
||||
}
|
||||
@@ -59,7 +72,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public void CopyFromSongInfo(SongInfo songInfo)
|
||||
{
|
||||
Title = songInfo.Title;
|
||||
Artists = songInfo.Artists;
|
||||
Artist = songInfo.Artist;
|
||||
Album = songInfo.Album;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class MappedSongSearchQuery : ObservableRecipient
|
||||
[Table("SongSearchMap")]
|
||||
[Index(nameof(OriginalTitle), nameof(OriginalArtist), nameof(OriginalAlbum))]
|
||||
public partial class MappedSongSearchQuery : ObservableRecipient, ICloneable
|
||||
{
|
||||
[Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)] public string Id { get; set; }
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalTitle { get; set; } = string.Empty;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalArtist { get; set; } = string.Empty;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalAlbum { get; set; } = string.Empty;
|
||||
@@ -17,7 +25,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider? LyricsSearchProvider { get; set; }
|
||||
|
||||
public MappedSongSearchQuery Clone()
|
||||
public object Clone()
|
||||
{
|
||||
return new MappedSongSearchQuery
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Entities
|
||||
{
|
||||
[Index(nameof(Title))]
|
||||
[Index(nameof(Artist))]
|
||||
@@ -1,5 +1,6 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models.Entities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -10,20 +11,6 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public string Uri { get; private set; } = "";
|
||||
|
||||
public string DecodedAbsoluteUri
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Uri)) return "";
|
||||
try
|
||||
{
|
||||
var u = new Uri(Uri);
|
||||
return u.IsFile ? u.LocalPath : System.Net.WebUtility.UrlDecode(u.AbsoluteUri);
|
||||
}
|
||||
catch { return Uri; }
|
||||
}
|
||||
}
|
||||
|
||||
public string? RawLyrics { get; set; }
|
||||
public string? LocalAlbumArtPath { get; set; }
|
||||
public byte[]? AlbumArtByteArray { get; set; }
|
||||
@@ -119,14 +106,13 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
public ExtendedTrack() : base() { }
|
||||
|
||||
public ExtendedTrack(string uriString) : base()
|
||||
public ExtendedTrack(string decodedUriString) : base()
|
||||
{
|
||||
Uri = uriString;
|
||||
|
||||
string atlPath = uriString;
|
||||
string atlPath = decodedUriString;
|
||||
try
|
||||
{
|
||||
var u = new Uri(uriString);
|
||||
var u = new Uri(decodedUriString);
|
||||
Uri = u.AbsoluteUri;
|
||||
if (u.IsFile) atlPath = u.LocalPath;
|
||||
}
|
||||
catch { }
|
||||
@@ -144,7 +130,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
this.Uri = entity.Uri;
|
||||
|
||||
this.Title = entity.Title;
|
||||
this.Artist = entity.Artists;
|
||||
this.Artist = entity.Artist;
|
||||
this.Album = entity.Album;
|
||||
this.Year = entity.Year;
|
||||
this.Bitrate = entity.Bitrate;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Http
|
||||
{
|
||||
public class CutletDockerRequest
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Http
|
||||
{
|
||||
public class CutletDockerResponse
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Http
|
||||
{
|
||||
public class LibreTranslateResponse
|
||||
{
|
||||
18
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Keyframe.cs
Normal file
18
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Keyframe.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public struct Keyframe<T>
|
||||
{
|
||||
public T Value { get; }
|
||||
public double Duration { get; }
|
||||
|
||||
public Keyframe(T value, double durationSeconds)
|
||||
{
|
||||
Value = value;
|
||||
Duration = durationSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||
{
|
||||
public class BaseLyrics
|
||||
{
|
||||
public int StartMs { get; set; }
|
||||
public int EndMs { get; set; }
|
||||
public int DurationMs => EndMs - StartMs;
|
||||
|
||||
public string Text { get; set; } = "";
|
||||
public int Length => Text.Length;
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public int EndIndex => StartIndex + Length - 1;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||
{
|
||||
public class BaseRenderLyrics : BaseLyrics
|
||||
{
|
||||
public bool IsPlayingLastFrame { get; set; } = false;
|
||||
|
||||
public BaseRenderLyrics(BaseLyrics baseLyrics)
|
||||
{
|
||||
this.Text = baseLyrics.Text;
|
||||
this.StartMs = baseLyrics.StartMs;
|
||||
this.EndMs = baseLyrics.EndMs;
|
||||
this.StartIndex = baseLyrics.StartIndex;
|
||||
}
|
||||
|
||||
public bool GetIsPlaying(double currentMs) => this.StartMs <= currentMs && currentMs < this.EndMs;
|
||||
public double GetPlayProgress(double currentMs) => Math.Clamp((currentMs - this.StartMs) / this.DurationMs, 0, 1);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||
{
|
||||
public class LyricsData
|
||||
{
|
||||
@@ -18,7 +18,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
set => field = value;
|
||||
}
|
||||
public bool AutoGenerated { get; set; } = false;
|
||||
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.OriginalText));
|
||||
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.PrimaryText));
|
||||
|
||||
public LyricsData()
|
||||
{
|
||||
@@ -35,7 +35,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
|
||||
OriginalText = _localizationService.GetLocalizedString("LyricsNotFound"),
|
||||
PrimaryText = _localizationService.GetLocalizedString("LyricsNotFound"),
|
||||
}]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||
{
|
||||
public class LyricsLine : BaseLyrics
|
||||
{
|
||||
public List<BaseLyrics> PrimarySyllables { get; set; } = [];
|
||||
public List<BaseLyrics> SecondarySyllables { get; set; } = [];
|
||||
public List<BaseLyrics> TertiarySyllables { get; set; } = [];
|
||||
|
||||
public List<BaseLyrics> PrimaryChars { get; private set; } = [];
|
||||
public List<BaseLyrics> SecondaryChars { get; private set; } = [];
|
||||
public List<BaseLyrics> TertiaryChars { get; private set; } = [];
|
||||
|
||||
public string PrimaryText { get; set; } = "";
|
||||
public string SecondaryText { get; set; } = "";
|
||||
public string TertiaryText { get; set; } = "";
|
||||
|
||||
public LyricsLine()
|
||||
{
|
||||
for (int charStartIndex = 0; charStartIndex < PrimaryText.Length; charStartIndex++)
|
||||
{
|
||||
var syllable = PrimarySyllables.FirstOrDefault(x => x.StartIndex <= charStartIndex && charStartIndex <= x.EndIndex);
|
||||
if (syllable == null) continue;
|
||||
|
||||
var avgCharDuration = syllable.DurationMs / syllable.Length;
|
||||
if (avgCharDuration == 0) continue;
|
||||
|
||||
var charStartMs = syllable.StartMs + (charStartIndex - syllable.StartIndex) * avgCharDuration;
|
||||
var charEndMs = charStartMs + avgCharDuration;
|
||||
|
||||
PrimaryChars.Add(new BaseLyrics
|
||||
{
|
||||
StartIndex = charStartIndex,
|
||||
StartMs = charStartMs,
|
||||
EndMs = charEndMs,
|
||||
Text = PrimaryText[charStartIndex].ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||
{
|
||||
public class RenderLyricsChar : BaseRenderLyrics
|
||||
{
|
||||
public Rect LayoutRect { get; private set; }
|
||||
|
||||
public ValueTransition<double> ScaleTransition { get; set; }
|
||||
public ValueTransition<double> GlowTransition { get; set; }
|
||||
public ValueTransition<double> FloatTransition { get; set; }
|
||||
|
||||
public double ProgressPlayed { get; set; } = 0; // 0~1
|
||||
|
||||
public RenderLyricsChar(BaseLyrics lyricsChars, Rect layoutRect) : base(lyricsChars)
|
||||
{
|
||||
ScaleTransition = new(
|
||||
initialValue: 1.0,
|
||||
defaultTotalDuration: Time.AnimationDuration.TotalSeconds,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
GlowTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: Time.AnimationDuration.TotalSeconds,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
FloatTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: Time.LongAnimationDuration.TotalSeconds,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
LayoutRect = layoutRect;
|
||||
}
|
||||
|
||||
public void Update(TimeSpan elapsedTime)
|
||||
{
|
||||
ScaleTransition.Update(elapsedTime);
|
||||
GlowTransition.Update(elapsedTime);
|
||||
FloatTransition.Update(elapsedTime);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||
{
|
||||
public class RenderLyricsLine : BaseRenderLyrics
|
||||
{
|
||||
public List<RenderLyricsChar> PrimaryRenderChars { get; private set; } = [];
|
||||
public List<RenderLyricsSyllable> PrimaryRenderSyllables { get; private set; }
|
||||
|
||||
public double AnimationDuration { get; set; } = 0.3;
|
||||
|
||||
public ValueTransition<double> AngleTransition { get; set; }
|
||||
public ValueTransition<double> BlurAmountTransition { get; set; }
|
||||
public ValueTransition<double> PhoneticOpacityTransition { get; set; }
|
||||
public ValueTransition<double> PlayedOriginalOpacityTransition { get; set; }
|
||||
public ValueTransition<double> UnplayedOriginalOpacityTransition { get; set; }
|
||||
public ValueTransition<double> TranslatedOpacityTransition { get; set; }
|
||||
public ValueTransition<double> ScaleTransition { get; set; }
|
||||
public ValueTransition<double> YOffsetTransition { get; set; }
|
||||
public ValueTransition<Color> ColorTransition { get; set; }
|
||||
|
||||
public CanvasTextLayout? PrimaryTextLayout { get; private set; }
|
||||
public CanvasTextLayout? SecondaryTextLayout { get; private set; }
|
||||
public CanvasTextLayout? TertiaryTextLayout { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原文坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 PrimaryPosition { get; set; }
|
||||
/// <summary>
|
||||
/// 译文坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 SecondaryPosition { get; set; }
|
||||
/// <summary>
|
||||
/// 注音坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 TertiaryPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 顶部坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 TopLeftPosition { get; set; }
|
||||
/// <summary>
|
||||
/// 中心坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 CenterPosition { get; private set; }
|
||||
/// <summary>
|
||||
/// 底部坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 BottomRightPosition { get; set; }
|
||||
|
||||
public CanvasGeometry? PrimaryCanvasGeometry { get; private set; }
|
||||
public CanvasGeometry? SecondaryCanvasGeometry { get; private set; }
|
||||
public CanvasGeometry? TertiaryCanvasGeometry { get; private set; }
|
||||
|
||||
public string PrimaryText { get; set; } = "";
|
||||
public string SecondaryText { get; set; } = "";
|
||||
public string TertiaryText { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 轨道索引 (0 = 主轨道, 1 = 第一副轨道, etc.)
|
||||
/// 用于布局计算时的堆叠逻辑
|
||||
/// </summary>
|
||||
public int LaneIndex { get; set; } = 0;
|
||||
|
||||
public RenderLyricsLine(LyricsLine lyricsLine) : base(lyricsLine)
|
||||
{
|
||||
AngleTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
BlurAmountTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
PhoneticOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
PlayedOriginalOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
UnplayedOriginalOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
TranslatedOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
ScaleTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
YOffsetTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
);
|
||||
ColorTransition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
defaultTotalDuration: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
|
||||
StartMs = lyricsLine.StartMs;
|
||||
EndMs = lyricsLine.EndMs;
|
||||
TertiaryText = lyricsLine.TertiaryText;
|
||||
PrimaryText = lyricsLine.PrimaryText;
|
||||
SecondaryText = lyricsLine.SecondaryText;
|
||||
PrimaryRenderSyllables = lyricsLine.PrimarySyllables.Select(x => new RenderLyricsSyllable(x)).ToList();
|
||||
}
|
||||
|
||||
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)
|
||||
{
|
||||
if (PrimaryTextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double centerY = (TopLeftPosition.Y + BottomRightPosition.Y) / 2;
|
||||
|
||||
CenterPosition = type switch
|
||||
{
|
||||
TextAlignmentType.Left => new Vector2(0, (float)centerY),
|
||||
TextAlignmentType.Center => new Vector2((float)(0 + maxWidth / 2.0), (float)centerY),
|
||||
TextAlignmentType.Right => new Vector2((float)(0 + maxWidth), (float)centerY),
|
||||
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
}
|
||||
|
||||
public void DisposeTextLayout()
|
||||
{
|
||||
TertiaryTextLayout?.Dispose();
|
||||
TertiaryTextLayout = null;
|
||||
|
||||
PrimaryTextLayout?.Dispose();
|
||||
PrimaryTextLayout = null;
|
||||
|
||||
SecondaryTextLayout?.Dispose();
|
||||
SecondaryTextLayout = null;
|
||||
}
|
||||
|
||||
public void RecreateTextLayout(
|
||||
ICanvasAnimatedControl control,
|
||||
bool createPhonetic, bool createTranslated,
|
||||
int phoneticTextFontSize, int originalTextFontSize, int translatedTextFontSize,
|
||||
LyricsFontWeight fontWeight,
|
||||
string fontFamilyCJK, string fontFamilyWestern,
|
||||
double maxWidth, double maxHeight, TextAlignmentType type)
|
||||
{
|
||||
DisposeTextLayout();
|
||||
|
||||
if (createPhonetic && TertiaryText != "")
|
||||
{
|
||||
TertiaryTextLayout = new CanvasTextLayout(control, TertiaryText, new CanvasTextFormat
|
||||
{
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Top,
|
||||
FontSize = phoneticTextFontSize,
|
||||
FontWeight = fontWeight.ToFontWeight(),
|
||||
}, (float)maxWidth, (float)maxHeight)
|
||||
{
|
||||
HorizontalAlignment = type.ToCanvasHorizontalAlignment(),
|
||||
};
|
||||
TertiaryTextLayout.SetFontFamily(TertiaryText, fontFamilyCJK, fontFamilyWestern);
|
||||
}
|
||||
|
||||
PrimaryTextLayout = new CanvasTextLayout(control, PrimaryText, new CanvasTextFormat
|
||||
{
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Top,
|
||||
FontSize = originalTextFontSize,
|
||||
FontWeight = fontWeight.ToFontWeight(),
|
||||
}, (float)maxWidth, (float)maxHeight)
|
||||
{
|
||||
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
|
||||
};
|
||||
PrimaryTextLayout.SetFontFamily(PrimaryText, fontFamilyCJK, fontFamilyWestern);
|
||||
|
||||
if (createTranslated && SecondaryText != "")
|
||||
{
|
||||
SecondaryTextLayout = new CanvasTextLayout(control, SecondaryText, new CanvasTextFormat
|
||||
{
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Top,
|
||||
FontSize = translatedTextFontSize,
|
||||
FontWeight = fontWeight.ToFontWeight(),
|
||||
}, (float)maxWidth, (float)maxHeight)
|
||||
{
|
||||
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
|
||||
};
|
||||
SecondaryTextLayout.SetFontFamily(SecondaryText, fontFamilyCJK, fontFamilyWestern);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisposeTextGeometry()
|
||||
{
|
||||
TertiaryCanvasGeometry?.Dispose();
|
||||
TertiaryCanvasGeometry = null;
|
||||
|
||||
PrimaryCanvasGeometry?.Dispose();
|
||||
PrimaryCanvasGeometry = null;
|
||||
|
||||
SecondaryCanvasGeometry?.Dispose();
|
||||
SecondaryCanvasGeometry = null;
|
||||
}
|
||||
|
||||
public void RecreateTextGeometry()
|
||||
{
|
||||
DisposeTextGeometry();
|
||||
|
||||
if (TertiaryTextLayout != null)
|
||||
{
|
||||
TertiaryCanvasGeometry = CanvasGeometry.CreateText(TertiaryTextLayout);
|
||||
}
|
||||
|
||||
if (PrimaryTextLayout != null)
|
||||
{
|
||||
PrimaryCanvasGeometry = CanvasGeometry.CreateText(PrimaryTextLayout);
|
||||
}
|
||||
|
||||
if (SecondaryTextLayout != null)
|
||||
{
|
||||
SecondaryCanvasGeometry = CanvasGeometry.CreateText(SecondaryTextLayout);
|
||||
}
|
||||
}
|
||||
|
||||
public void RecreateRenderChars()
|
||||
{
|
||||
PrimaryRenderChars.Clear();
|
||||
if (PrimaryTextLayout == null) return;
|
||||
|
||||
foreach (var syllable in PrimaryRenderSyllables)
|
||||
{
|
||||
syllable.ChildrenRenderLyricsChars.Clear();
|
||||
}
|
||||
|
||||
var textLength = PrimaryText.Length;
|
||||
|
||||
for (int startCharIndex = 0; startCharIndex < textLength; startCharIndex++)
|
||||
{
|
||||
var region = PrimaryTextLayout.GetCharacterRegions(startCharIndex, 1).FirstOrDefault();
|
||||
var bounds = region.LayoutBounds;
|
||||
|
||||
var syllable = PrimaryRenderSyllables.FirstOrDefault(x => x.StartIndex <= startCharIndex && startCharIndex <= x.EndIndex);
|
||||
if (syllable == null) continue;
|
||||
|
||||
var avgCharDuration = syllable.DurationMs / syllable.Length;
|
||||
var charStartMs = syllable.StartMs + (startCharIndex - syllable.StartIndex) * avgCharDuration;
|
||||
var charEndMs = charStartMs + avgCharDuration;
|
||||
|
||||
var renderLyricsChar = new RenderLyricsChar(new BaseLyrics
|
||||
{
|
||||
StartIndex = startCharIndex,
|
||||
Text = PrimaryText[startCharIndex].ToString(),
|
||||
StartMs = charStartMs,
|
||||
EndMs = charEndMs,
|
||||
}, bounds);
|
||||
|
||||
syllable.ChildrenRenderLyricsChars.Add(renderLyricsChar);
|
||||
|
||||
PrimaryRenderChars.Add(renderLyricsChar);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(TimeSpan elapsedTime)
|
||||
{
|
||||
AngleTransition.Update(elapsedTime);
|
||||
ScaleTransition.Update(elapsedTime);
|
||||
BlurAmountTransition.Update(elapsedTime);
|
||||
PhoneticOpacityTransition.Update(elapsedTime);
|
||||
PlayedOriginalOpacityTransition.Update(elapsedTime);
|
||||
UnplayedOriginalOpacityTransition.Update(elapsedTime);
|
||||
TranslatedOpacityTransition.Update(elapsedTime);
|
||||
YOffsetTransition.Update(elapsedTime);
|
||||
ColorTransition.Update(elapsedTime);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||
{
|
||||
public class RenderLyricsSyllable : BaseRenderLyrics
|
||||
{
|
||||
public List<RenderLyricsChar> ChildrenRenderLyricsChars { get; set; } = [];
|
||||
|
||||
public RenderLyricsSyllable(BaseLyrics lyricsSyllable) : base(lyricsSyllable) { }
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class LyricsLine
|
||||
{
|
||||
public List<LyricsSyllable> LyricsSyllables { get; set; } = [];
|
||||
|
||||
public int? DurationMs => EndMs - StartMs;
|
||||
public int? EndMs { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原文
|
||||
/// </summary>
|
||||
public string OriginalText { get; set; } = "";
|
||||
/// <summary>
|
||||
/// 译文
|
||||
/// </summary>
|
||||
public string TranslatedText { get; set; } = "";
|
||||
/// <summary>
|
||||
/// 注音
|
||||
/// </summary>
|
||||
public string PhoneticText { get; set; } = "";
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class LyricsSyllable
|
||||
{
|
||||
public int? EndMs { get; set; }
|
||||
public int StartIndex { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
public string Text { get; set; } = string.Empty;
|
||||
public int? DurationMs => EndMs - StartMs;
|
||||
public bool IsLongDuration => DurationMs >= 700;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class PlayerStatDisplayItem
|
||||
{
|
||||
public string PlayerId { get; set; }
|
||||
public int PlayCount { get; set; }
|
||||
|
||||
public string PlayerName => PlayerIdHelper.GetDisplayName(PlayerId);
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using System.Numerics;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class RenderLyricsLine : LyricsLine
|
||||
{
|
||||
public double AnimationDuration { get; set; } = 0.3;
|
||||
public ValueTransition<double> AngleTransition { get; set; }
|
||||
public ValueTransition<double> BlurAmountTransition { get; set; }
|
||||
public ValueTransition<double> PhoneticOpacityTransition { get; set; }
|
||||
public ValueTransition<double> PlayedOriginalOpacityTransition { get; set; }
|
||||
public ValueTransition<double> UnplayedOriginalOpacityTransition { get; set; }
|
||||
public ValueTransition<double> TranslatedOpacityTransition { get; set; }
|
||||
public ValueTransition<double> ScaleTransition { get; set; }
|
||||
public ValueTransition<double> YOffsetTransition { get; set; }
|
||||
public ValueTransition<Color> ColorTransition { get; set; }
|
||||
|
||||
public CanvasTextLayout? OriginalCanvasTextLayout { get; private set; }
|
||||
public CanvasTextLayout? TranslatedCanvasTextLayout { get; private set; }
|
||||
public CanvasTextLayout? PhoneticCanvasTextLayout { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原文坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 OriginalPosition { get; set; }
|
||||
/// <summary>
|
||||
/// 译文坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 TranslatedPosition { get; set; }
|
||||
/// <summary>
|
||||
/// 注音坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 PhoneticPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 顶部坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 TopLeftPosition { get; set; }
|
||||
/// <summary>
|
||||
/// 中心坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 CenterPosition { get; private set; }
|
||||
/// <summary>
|
||||
/// 底部坐标(相对于坐标原点)
|
||||
/// </summary>
|
||||
public Vector2 BottomRightPosition { get; set; }
|
||||
|
||||
public CanvasGeometry? OriginalCanvasGeometry { get; private set; }
|
||||
public CanvasGeometry? TranslatedCanvasGeometry { get; private set; }
|
||||
public CanvasGeometry? PhoneticCanvasGeometry { get; private set; }
|
||||
|
||||
public RenderLyricsLine()
|
||||
{
|
||||
AngleTransition = new(
|
||||
initialValue: 0,
|
||||
durationSeconds: AnimationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
BlurAmountTransition = new(
|
||||
initialValue: 0,
|
||||
durationSeconds: AnimationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
PhoneticOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
durationSeconds: AnimationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
PlayedOriginalOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
durationSeconds: AnimationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
UnplayedOriginalOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
durationSeconds: AnimationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
TranslatedOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
durationSeconds: AnimationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
ScaleTransition = new(
|
||||
initialValue: 0,
|
||||
durationSeconds: AnimationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
YOffsetTransition = new(
|
||||
initialValue: 0,
|
||||
durationSeconds: AnimationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
ColorTransition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
}
|
||||
|
||||
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)
|
||||
{
|
||||
if (OriginalCanvasTextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double centerY = (TopLeftPosition.Y + BottomRightPosition.Y) / 2;
|
||||
|
||||
CenterPosition = type switch
|
||||
{
|
||||
TextAlignmentType.Left => new Vector2(0, (float)centerY),
|
||||
TextAlignmentType.Center => new Vector2((float)(0 + maxWidth / 2.0), (float)centerY),
|
||||
TextAlignmentType.Right => new Vector2((float)(0 + maxWidth), (float)centerY),
|
||||
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
}
|
||||
|
||||
public void DisposeTextLayout()
|
||||
{
|
||||
PhoneticCanvasTextLayout?.Dispose();
|
||||
PhoneticCanvasTextLayout = null;
|
||||
|
||||
OriginalCanvasTextLayout?.Dispose();
|
||||
OriginalCanvasTextLayout = null;
|
||||
|
||||
TranslatedCanvasTextLayout?.Dispose();
|
||||
TranslatedCanvasTextLayout = null;
|
||||
}
|
||||
|
||||
public void RecreateTextLayout(
|
||||
ICanvasAnimatedControl control,
|
||||
bool createPhonetic, bool createTranslated,
|
||||
int phoneticTextFontSize, int originalTextFontSize, int translatedTextFontSize,
|
||||
LyricsFontWeight fontWeight,
|
||||
string fontFamilyCJK, string fontFamilyWestern,
|
||||
double maxWidth, double maxHeight, TextAlignmentType type)
|
||||
{
|
||||
DisposeTextLayout();
|
||||
|
||||
if (createPhonetic && PhoneticText != "")
|
||||
{
|
||||
PhoneticCanvasTextLayout = new CanvasTextLayout(control, PhoneticText, new CanvasTextFormat
|
||||
{
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Top,
|
||||
FontSize = phoneticTextFontSize,
|
||||
FontWeight = fontWeight.ToFontWeight(),
|
||||
}, (float)maxWidth, (float)maxHeight)
|
||||
{
|
||||
HorizontalAlignment = type.ToCanvasHorizontalAlignment(),
|
||||
};
|
||||
PhoneticCanvasTextLayout.SetFontFamily(PhoneticText, fontFamilyCJK, fontFamilyWestern);
|
||||
}
|
||||
|
||||
OriginalCanvasTextLayout = new CanvasTextLayout(control, OriginalText, new CanvasTextFormat
|
||||
{
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Top,
|
||||
FontSize = originalTextFontSize,
|
||||
FontWeight = fontWeight.ToFontWeight(),
|
||||
}, (float)maxWidth, (float)maxHeight)
|
||||
{
|
||||
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
|
||||
};
|
||||
OriginalCanvasTextLayout.SetFontFamily(OriginalText, fontFamilyCJK, fontFamilyWestern);
|
||||
|
||||
if (createTranslated && TranslatedText != "")
|
||||
{
|
||||
TranslatedCanvasTextLayout = new CanvasTextLayout(control, TranslatedText, new CanvasTextFormat
|
||||
{
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Top,
|
||||
FontSize = translatedTextFontSize,
|
||||
FontWeight = fontWeight.ToFontWeight(),
|
||||
}, (float)maxWidth, (float)maxHeight)
|
||||
{
|
||||
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
|
||||
};
|
||||
TranslatedCanvasTextLayout.SetFontFamily(TranslatedText, fontFamilyCJK, fontFamilyWestern);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisposeTextGeometry()
|
||||
{
|
||||
PhoneticCanvasGeometry?.Dispose();
|
||||
PhoneticCanvasGeometry = null;
|
||||
|
||||
OriginalCanvasGeometry?.Dispose();
|
||||
OriginalCanvasGeometry = null;
|
||||
|
||||
TranslatedCanvasGeometry?.Dispose();
|
||||
TranslatedCanvasGeometry = null;
|
||||
}
|
||||
|
||||
public void RecreateTextGeometry()
|
||||
{
|
||||
DisposeTextGeometry();
|
||||
|
||||
if (PhoneticCanvasTextLayout != null)
|
||||
{
|
||||
PhoneticCanvasGeometry = CanvasGeometry.CreateText(PhoneticCanvasTextLayout);
|
||||
}
|
||||
|
||||
if (OriginalCanvasTextLayout != null)
|
||||
{
|
||||
OriginalCanvasGeometry = CanvasGeometry.CreateText(OriginalCanvasTextLayout);
|
||||
}
|
||||
|
||||
if (TranslatedCanvasTextLayout != null)
|
||||
{
|
||||
TranslatedCanvasGeometry = CanvasGeometry.CreateText(TranslatedCanvasTextLayout);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
public partial class AlbumArtSearchProviderInfo : ObservableRecipient
|
||||
{
|
||||
@@ -1,5 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
@@ -14,7 +15,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MediaFolder> LocalMediaFolders { get; set; } = [];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; } = [];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MappedSongSearchQuery> MappedSongSearchQueries { get; set; } = [];
|
||||
[Obsolete][ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MappedSongSearchQuery> MappedSongSearchQueries { get; set; } = [];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsWindowStatus> WindowBoundsRecords { get; set; } = [];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<SongsTabInfo> StarredPlaylists { get; set; } = [];
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LanguageCode { get; set; } = "";
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LXMusicServer { get; set; } = string.Empty;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string AmllTtmlDbBaseUrl { get; set; } = Constants.AmllTTmlDB.BaseUrl;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsForceWordByWordEffect { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ShowOrHideLyricsWindowShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "H" };
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnLyricsWindowClosed { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ListenOnNewPlaybackSource { get; set; } = true;
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
public partial class LyricsEffectSettings : ObservableRecipient, ICloneable
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial WordByWordEffectMode WordByWordEffectMode { get; set; } = WordByWordEffectMode.Auto;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsBlurEffectEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFadeOutEffectEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsOutOfSightEffectEnabled { get; set; } = true;
|
||||
@@ -24,6 +26,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFloatAnimationEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFloatAnimationAmountAutoAdjust { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFloatAnimationAmount { get; set; } = 8;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFloatAnimationDuration { get; set; } = 450; // 450ms
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial EasingType LyricsScrollEasingType { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollDuration { get; set; }
|
||||
@@ -53,6 +56,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
return new LyricsEffectSettings(this.LyricsScrollTopDuration, this.LyricsScrollDuration, this.LyricsScrollBottomDuration, this.LyricsScrollEasingType)
|
||||
{
|
||||
WordByWordEffectMode = this.WordByWordEffectMode,
|
||||
|
||||
IsLyricsBlurEffectEnabled = this.IsLyricsBlurEffectEnabled,
|
||||
IsLyricsFadeOutEffectEnabled = this.IsLyricsFadeOutEffectEnabled,
|
||||
IsLyricsOutOfSightEffectEnabled = this.IsLyricsOutOfSightEffectEnabled,
|
||||
@@ -71,6 +76,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
IsLyricsFloatAnimationEnabled = this.IsLyricsFloatAnimationEnabled,
|
||||
IsLyricsFloatAnimationAmountAutoAdjust = this.IsLyricsFloatAnimationAmountAutoAdjust,
|
||||
LyricsFloatAnimationAmount = this.LyricsFloatAnimationAmount,
|
||||
LyricsFloatAnimationDuration = this.LyricsFloatAnimationDuration,
|
||||
|
||||
LyricsScrollEasingType = this.LyricsScrollEasingType,
|
||||
LyricsScrollDuration = this.LyricsScrollDuration,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
public partial class LyricsSearchProviderInfo : ObservableRecipient
|
||||
{
|
||||
@@ -11,6 +11,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider Provider { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMatchingThresholdOverwritten { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 40;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IgnoreCacheWhenSearching { get; set; } = false;
|
||||
|
||||
public LyricsSearchProviderInfo() { }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
@@ -10,7 +9,7 @@ using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
public partial class LyricsWindowStatus : ObservableRecipient, ICloneable
|
||||
{
|
||||
@@ -6,9 +6,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
public partial class MediaFolder : ObservableRecipient
|
||||
{
|
||||
@@ -8,7 +8,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
public partial class MediaSourceProviderInfo : ObservableRecipient
|
||||
{
|
||||
@@ -1,8 +1,7 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
public partial class SongsTabInfo : BaseViewModel
|
||||
{
|
||||
@@ -1,18 +1,17 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using NTextCat.Commons;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class SongInfo : ObservableObject, ICloneable
|
||||
public partial class SongInfo : ObservableRecipient, ICloneable
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial string Album { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string[] Artists { get; set; }
|
||||
public partial string Artist { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double DurationMs { get; set; }
|
||||
@@ -26,12 +25,12 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string? SongId { get; set; } = null;
|
||||
|
||||
[ObservableProperty] public partial long StartedAt { get; set; } = DateTime.Now.ToBinary();
|
||||
|
||||
public string? LinkedFileName { get; set; } = null;
|
||||
|
||||
public double Duration => DurationMs / 1000;
|
||||
|
||||
public string DisplayArtists => Artists.Join(ATL.Settings.DisplayValueSeparator.ToString());
|
||||
|
||||
public SongInfo() { }
|
||||
|
||||
public object Clone()
|
||||
@@ -39,12 +38,13 @@ namespace BetterLyrics.WinUI3.Models
|
||||
return new SongInfo()
|
||||
{
|
||||
Title = this.Title,
|
||||
Artists = this.Artists,
|
||||
Artist = this.Artist,
|
||||
Album = this.Album,
|
||||
DurationMs = this.DurationMs,
|
||||
PlayerId = this.PlayerId,
|
||||
SongId = this.SongId,
|
||||
LinkedFileName = this.LinkedFileName,
|
||||
StartedAt = this.StartedAt,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
return
|
||||
$"Title: {Title}, " +
|
||||
$"Artist: {DisplayArtists}, " +
|
||||
$"Artist: {Artist}, " +
|
||||
$"Album: {Album}, " +
|
||||
$"Duration: {Duration} sec, " +
|
||||
$"Plauer ID: {PlayerId}, " +
|
||||
@@ -62,7 +62,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
public string ToFileName()
|
||||
{
|
||||
return $"{DisplayArtists} - {Title} - {Album} - {Duration}";
|
||||
return $"{Artist} - {Title} - {Album} - {Duration}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user