mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2026-02-02 09:08:26 +00:00
feat: Create a new GUI project based on Avalonia. (Powered by Claude)
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Localization;
|
||||
|
||||
/// <summary>
|
||||
/// Provides localization services for CLI commands and options.
|
||||
/// </summary>
|
||||
public interface ICliLocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a localized string by key.
|
||||
/// </summary>
|
||||
string this[string key] { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of CLI localization service.
|
||||
/// </summary>
|
||||
public class CliLocalizationService : ICliLocalizationService, ISingletonDependency
|
||||
{
|
||||
private readonly IStringLocalizer<Cli.CommandStrings> _localizer;
|
||||
|
||||
public CliLocalizationService(IStringLocalizer<Cli.CommandStrings> localizer)
|
||||
{
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = _localizer[key];
|
||||
return value.ResourceNotFound ? $"[{key}]" : value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/ZonyLrcTools.Cli/Resources/CommandStrings.cs
Normal file
10
src/ZonyLrcTools.Cli/Resources/CommandStrings.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace ZonyLrcTools.Cli;
|
||||
|
||||
/// <summary>
|
||||
/// Marker class for CLI command localization resources.
|
||||
/// This class is used by IStringLocalizer<CommandStrings> to locate the resource files.
|
||||
/// The namespace must be the root namespace so that IStringLocalizer looks for Resources/CommandStrings.resx
|
||||
/// </summary>
|
||||
public class CommandStrings
|
||||
{
|
||||
}
|
||||
92
src/ZonyLrcTools.Cli/Resources/CommandStrings.en-US.resx
Normal file
92
src/ZonyLrcTools.Cli/Resources/CommandStrings.en-US.resx
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Main Program -->
|
||||
<data name="Program_Description" xml:space="preserve">
|
||||
<value>ZonyLrcTools - Lyrics download command-line tool</value>
|
||||
</data>
|
||||
<!-- Download Command -->
|
||||
<data name="Download_Description" xml:space="preserve">
|
||||
<value>Download lyrics or album covers</value>
|
||||
</data>
|
||||
<data name="Download_Dir_Description" xml:space="preserve">
|
||||
<value>Specify the folder path containing music files</value>
|
||||
</data>
|
||||
<data name="Download_Lyrics_Description" xml:space="preserve">
|
||||
<value>Download lyrics files</value>
|
||||
</data>
|
||||
<data name="Download_Album_Description" xml:space="preserve">
|
||||
<value>Download album covers</value>
|
||||
</data>
|
||||
<data name="Download_CSV_Description" xml:space="preserve">
|
||||
<value>Import song list from CSV file for download</value>
|
||||
</data>
|
||||
<data name="Download_SongList_Description" xml:space="preserve">
|
||||
<value>Import song list from NetEase playlist ID for download</value>
|
||||
</data>
|
||||
<!-- Utility Command -->
|
||||
<data name="Utility_Description" xml:space="preserve">
|
||||
<value>Utility commands</value>
|
||||
</data>
|
||||
<data name="Utility_Convert_Description" xml:space="preserve">
|
||||
<value>Convert encrypted music files to normal format</value>
|
||||
</data>
|
||||
<data name="Utility_Source_Description" xml:space="preserve">
|
||||
<value>Specify the folder path to convert</value>
|
||||
</data>
|
||||
<!-- Search Command -->
|
||||
<data name="Search_Description" xml:space="preserve">
|
||||
<value>Search for lyrics</value>
|
||||
</data>
|
||||
<data name="Search_Name_Description" xml:space="preserve">
|
||||
<value>Song name</value>
|
||||
</data>
|
||||
<data name="Search_Artist_Description" xml:space="preserve">
|
||||
<value>Artist name</value>
|
||||
</data>
|
||||
<!-- Common Options -->
|
||||
<data name="Option_Help" xml:space="preserve">
|
||||
<value>Show help information</value>
|
||||
</data>
|
||||
<data name="Option_Version" xml:space="preserve">
|
||||
<value>Show version information</value>
|
||||
</data>
|
||||
</root>
|
||||
92
src/ZonyLrcTools.Cli/Resources/CommandStrings.resx
Normal file
92
src/ZonyLrcTools.Cli/Resources/CommandStrings.resx
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Main Program -->
|
||||
<data name="Program_Description" xml:space="preserve">
|
||||
<value>ZonyLrcTools - 歌词下载命令行工具</value>
|
||||
</data>
|
||||
<!-- Download Command -->
|
||||
<data name="Download_Description" xml:space="preserve">
|
||||
<value>下载歌词或专辑封面</value>
|
||||
</data>
|
||||
<data name="Download_Dir_Description" xml:space="preserve">
|
||||
<value>指定音乐文件所在的文件夹路径</value>
|
||||
</data>
|
||||
<data name="Download_Lyrics_Description" xml:space="preserve">
|
||||
<value>下载歌词文件</value>
|
||||
</data>
|
||||
<data name="Download_Album_Description" xml:space="preserve">
|
||||
<value>下载专辑封面</value>
|
||||
</data>
|
||||
<data name="Download_CSV_Description" xml:space="preserve">
|
||||
<value>从 CSV 文件导入歌曲列表进行下载</value>
|
||||
</data>
|
||||
<data name="Download_SongList_Description" xml:space="preserve">
|
||||
<value>从网易云歌单 ID 导入歌曲列表进行下载</value>
|
||||
</data>
|
||||
<!-- Utility Command -->
|
||||
<data name="Utility_Description" xml:space="preserve">
|
||||
<value>工具类命令</value>
|
||||
</data>
|
||||
<data name="Utility_Convert_Description" xml:space="preserve">
|
||||
<value>转换加密音乐文件为普通格式</value>
|
||||
</data>
|
||||
<data name="Utility_Source_Description" xml:space="preserve">
|
||||
<value>指定需要转换的文件夹路径</value>
|
||||
</data>
|
||||
<!-- Search Command -->
|
||||
<data name="Search_Description" xml:space="preserve">
|
||||
<value>搜索歌词</value>
|
||||
</data>
|
||||
<data name="Search_Name_Description" xml:space="preserve">
|
||||
<value>歌曲名称</value>
|
||||
</data>
|
||||
<data name="Search_Artist_Description" xml:space="preserve">
|
||||
<value>艺术家名称</value>
|
||||
</data>
|
||||
<!-- Common Options -->
|
||||
<data name="Option_Help" xml:space="preserve">
|
||||
<value>显示帮助信息</value>
|
||||
</data>
|
||||
<data name="Option_Version" xml:space="preserve">
|
||||
<value>显示版本信息</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace ZonyLrcTools.Common.Infrastructure.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// 错误码帮助类接口。
|
||||
/// </summary>
|
||||
public interface IErrorCodeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取错误消息。
|
||||
/// </summary>
|
||||
/// <param name="errorCode">错误代码。</param>
|
||||
/// <returns>对应的错误消息。</returns>
|
||||
string GetMessage(int errorCode);
|
||||
|
||||
/// <summary>
|
||||
/// 获取警告消息。
|
||||
/// </summary>
|
||||
/// <param name="warningCode">警告代码。</param>
|
||||
/// <returns>对应的警告消息。</returns>
|
||||
string GetWarningMessage(int warningCode);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace ZonyLrcTools.Common.Infrastructure.Localization;
|
||||
|
||||
/// <summary>
|
||||
/// Provides localization services for the application.
|
||||
/// </summary>
|
||||
public interface ILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a localized string by key.
|
||||
/// </summary>
|
||||
string this[string key] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localized string by key with format arguments.
|
||||
/// </summary>
|
||||
string this[string key, params object[] args] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an error message by error code.
|
||||
/// </summary>
|
||||
string GetErrorMessage(int errorCode);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a warning message by warning code.
|
||||
/// </summary>
|
||||
string GetWarningMessage(int warningCode);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current culture.
|
||||
/// </summary>
|
||||
void SetCulture(string cultureName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current culture name.
|
||||
/// </summary>
|
||||
string CurrentCulture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of supported cultures.
|
||||
/// </summary>
|
||||
IReadOnlyList<CultureInfo> SupportedCultures { get; }
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
||||
|
||||
namespace ZonyLrcTools.Common.Infrastructure.Localization;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ILocalizationService"/> using .NET localization.
|
||||
/// </summary>
|
||||
public class LocalizationService : ILocalizationService, ISingletonDependency
|
||||
{
|
||||
private readonly IStringLocalizer<Common.Messages> _messagesLocalizer;
|
||||
private readonly IStringLocalizer<Common.ErrorMessages> _errorLocalizer;
|
||||
|
||||
private static readonly CultureInfo[] _supportedCultures =
|
||||
{
|
||||
new("zh-CN"),
|
||||
new("en-US")
|
||||
};
|
||||
|
||||
public LocalizationService(
|
||||
IStringLocalizer<Common.Messages> messagesLocalizer,
|
||||
IStringLocalizer<Common.ErrorMessages> errorLocalizer)
|
||||
{
|
||||
_messagesLocalizer = messagesLocalizer;
|
||||
_errorLocalizer = errorLocalizer;
|
||||
}
|
||||
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = _messagesLocalizer[key];
|
||||
return value.ResourceNotFound ? $"[{key}]" : value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public string this[string key, params object[] args]
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = _messagesLocalizer[key];
|
||||
if (value.ResourceNotFound) return $"[{key}]";
|
||||
return string.Format(value.Value, args);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetErrorMessage(int errorCode)
|
||||
{
|
||||
var key = $"Error_{errorCode}";
|
||||
var value = _errorLocalizer[key];
|
||||
return value.ResourceNotFound ? $"Unknown error: {errorCode}" : value.Value;
|
||||
}
|
||||
|
||||
public string GetWarningMessage(int warningCode)
|
||||
{
|
||||
var key = $"Warning_{warningCode}";
|
||||
var value = _errorLocalizer[key];
|
||||
return value.ResourceNotFound ? $"Unknown warning: {warningCode}" : value.Value;
|
||||
}
|
||||
|
||||
public string CurrentCulture => CultureInfo.CurrentUICulture.Name;
|
||||
|
||||
public IReadOnlyList<CultureInfo> SupportedCultures => _supportedCultures;
|
||||
|
||||
public void SetCulture(string cultureName)
|
||||
{
|
||||
var culture = new CultureInfo(cultureName);
|
||||
CultureInfo.CurrentCulture = culture;
|
||||
CultureInfo.CurrentUICulture = culture;
|
||||
}
|
||||
}
|
||||
10
src/ZonyLrcTools.Common/Resources/ErrorMessages.cs
Normal file
10
src/ZonyLrcTools.Common/Resources/ErrorMessages.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace ZonyLrcTools.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Marker class for error message localization resources.
|
||||
/// This class is used by IStringLocalizer<ErrorMessages> to locate the resource files.
|
||||
/// The namespace must be the root namespace so that IStringLocalizer looks for Resources/ErrorMessages.resx
|
||||
/// </summary>
|
||||
public class ErrorMessages
|
||||
{
|
||||
}
|
||||
95
src/ZonyLrcTools.Common/Resources/ErrorMessages.en-US.resx
Normal file
95
src/ZonyLrcTools.Common/Resources/ErrorMessages.en-US.resx
Normal file
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Error Messages -->
|
||||
<data name="Error_10001" xml:space="preserve">
|
||||
<value>The file extension to search cannot be empty.</value>
|
||||
</data>
|
||||
<data name="Error_10002" xml:space="preserve">
|
||||
<value>The directory to scan does not exist, please verify the path.</value>
|
||||
</data>
|
||||
<data name="Error_10003" xml:space="preserve">
|
||||
<value>Unable to get the file extension information.</value>
|
||||
</data>
|
||||
<data name="Error_10004" xml:space="preserve">
|
||||
<value>No music files were found.</value>
|
||||
</data>
|
||||
<data name="Error_10005" xml:space="preserve">
|
||||
<value>The specified encoding is not supported, please check the configuration.</value>
|
||||
</data>
|
||||
<data name="Error_10006" xml:space="preserve">
|
||||
<value>Unable to get the song list from NetEase Music.</value>
|
||||
</data>
|
||||
<!-- Warning Messages -->
|
||||
<data name="Warning_50001" xml:space="preserve">
|
||||
<value>An error occurred while scanning files.</value>
|
||||
</data>
|
||||
<data name="Warning_50002" xml:space="preserve">
|
||||
<value>Both song name and artist name are empty, unable to search.</value>
|
||||
</data>
|
||||
<data name="Warning_50003" xml:space="preserve">
|
||||
<value>Song name cannot be empty, unable to search.</value>
|
||||
</data>
|
||||
<data name="Warning_50004" xml:space="preserve">
|
||||
<value>The downloader did not find the corresponding song information.</value>
|
||||
</data>
|
||||
<data name="Warning_50005" xml:space="preserve">
|
||||
<value>The download request returned an invalid response, possibly a server error.</value>
|
||||
</data>
|
||||
<data name="Warning_50006" xml:space="preserve">
|
||||
<value>Tag info reader is empty, unable to parse music tag information.</value>
|
||||
</data>
|
||||
<data name="Warning_50007" xml:space="preserve">
|
||||
<value>TagLib tag reader encountered an unexpected exception.</value>
|
||||
</data>
|
||||
<data name="Warning_50008" xml:space="preserve">
|
||||
<value>Service interface restricted, unable to make request, please try using a proxy server.</value>
|
||||
</data>
|
||||
<data name="Warning_50009" xml:space="preserve">
|
||||
<value>HTTP request to target server failed.</value>
|
||||
</data>
|
||||
<data name="Warning_50010" xml:space="preserve">
|
||||
<value>Failed to deserialize HTTP response to JSON.</value>
|
||||
</data>
|
||||
<data name="Warning_50011" xml:space="preserve">
|
||||
<value>Currently only NCM format song conversion is supported.</value>
|
||||
</data>
|
||||
</root>
|
||||
95
src/ZonyLrcTools.Common/Resources/ErrorMessages.resx
Normal file
95
src/ZonyLrcTools.Common/Resources/ErrorMessages.resx
Normal file
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Error Messages -->
|
||||
<data name="Error_10001" xml:space="preserve">
|
||||
<value>待搜索的后缀不能为空。</value>
|
||||
</data>
|
||||
<data name="Error_10002" xml:space="preserve">
|
||||
<value>需要扫描的目录不存在,请确认路径是否正确。</value>
|
||||
</data>
|
||||
<data name="Error_10003" xml:space="preserve">
|
||||
<value>不能获取文件的后缀信息。</value>
|
||||
</data>
|
||||
<data name="Error_10004" xml:space="preserve">
|
||||
<value>没有扫描到任何音乐文件。</value>
|
||||
</data>
|
||||
<data name="Error_10005" xml:space="preserve">
|
||||
<value>指定的编码不受支持,请检查配置。</value>
|
||||
</data>
|
||||
<data name="Error_10006" xml:space="preserve">
|
||||
<value>无法从网易云音乐获取歌曲列表。</value>
|
||||
</data>
|
||||
<!-- Warning Messages -->
|
||||
<data name="Warning_50001" xml:space="preserve">
|
||||
<value>扫描文件时出现了错误。</value>
|
||||
</data>
|
||||
<data name="Warning_50002" xml:space="preserve">
|
||||
<value>歌曲名称或歌手名称均为空,无法进行搜索。</value>
|
||||
</data>
|
||||
<data name="Warning_50003" xml:space="preserve">
|
||||
<value>歌曲名称不能为空,无法进行搜索。</value>
|
||||
</data>
|
||||
<data name="Warning_50004" xml:space="preserve">
|
||||
<value>下载器没有搜索到对应的歌曲信息。</value>
|
||||
</data>
|
||||
<data name="Warning_50005" xml:space="preserve">
|
||||
<value>下载请求的返回值不合法,可能是服务端故障。</value>
|
||||
</data>
|
||||
<data name="Warning_50006" xml:space="preserve">
|
||||
<value>标签信息读取器为空,无法解析音乐 Tag 信息。</value>
|
||||
</data>
|
||||
<data name="Warning_50007" xml:space="preserve">
|
||||
<value>TagLib 标签读取器出现了预期之外的异常。</value>
|
||||
</data>
|
||||
<data name="Warning_50008" xml:space="preserve">
|
||||
<value>服务接口限制,无法进行请求,请尝试使用代理服务器。</value>
|
||||
</data>
|
||||
<data name="Warning_50009" xml:space="preserve">
|
||||
<value>对目标服务器执行 HTTP 请求失败。</value>
|
||||
</data>
|
||||
<data name="Warning_50010" xml:space="preserve">
|
||||
<value>HTTP 请求的结果反序列化为 JSON 失败。</value>
|
||||
</data>
|
||||
<data name="Warning_50011" xml:space="preserve">
|
||||
<value>目前仅支持 NCM 格式的歌曲转换操作。</value>
|
||||
</data>
|
||||
</root>
|
||||
10
src/ZonyLrcTools.Common/Resources/Messages.cs
Normal file
10
src/ZonyLrcTools.Common/Resources/Messages.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace ZonyLrcTools.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Marker class for general message localization resources.
|
||||
/// This class is used by IStringLocalizer<Messages> to locate the resource files.
|
||||
/// The namespace must be the root namespace so that IStringLocalizer looks for Resources/Messages.resx
|
||||
/// </summary>
|
||||
public class Messages
|
||||
{
|
||||
}
|
||||
82
src/ZonyLrcTools.Common/Resources/Messages.en-US.resx
Normal file
82
src/ZonyLrcTools.Common/Resources/Messages.en-US.resx
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Lyrics Download -->
|
||||
<data name="Lyrics_DownloadStart" xml:space="preserve">
|
||||
<value>Starting lyrics file download...</value>
|
||||
</data>
|
||||
<data name="Lyrics_DownloadComplete" xml:space="preserve">
|
||||
<value>Lyrics download complete, success: {0} skipped (instrumental): {1} failed: {2}.</value>
|
||||
</data>
|
||||
<data name="Lyrics_SongDownloadSuccess" xml:space="preserve">
|
||||
<value>Song: {0}, Artist: {1}, download successful.</value>
|
||||
</data>
|
||||
<data name="Lyrics_SongDownloadFailed" xml:space="preserve">
|
||||
<value>Song: {0}, Artist: {1}, download failed.</value>
|
||||
</data>
|
||||
<!-- Album Download -->
|
||||
<data name="Album_DownloadStart" xml:space="preserve">
|
||||
<value>Starting album cover download...</value>
|
||||
</data>
|
||||
<data name="Album_DownloadComplete" xml:space="preserve">
|
||||
<value>Album cover download complete, success: {0} failed: {1}.</value>
|
||||
</data>
|
||||
<!-- File Scanner -->
|
||||
<data name="Scanner_Start" xml:space="preserve">
|
||||
<value>Starting folder scan, please wait...</value>
|
||||
</data>
|
||||
<data name="Scanner_Complete" xml:space="preserve">
|
||||
<value>Scan complete, {0} files found.</value>
|
||||
</data>
|
||||
<!-- General -->
|
||||
<data name="General_Starting" xml:space="preserve">
|
||||
<value>Starting...</value>
|
||||
</data>
|
||||
<data name="General_UnhandledException" xml:space="preserve">
|
||||
<value>An unhandled exception occurred.</value>
|
||||
</data>
|
||||
<data name="General_ErrorCode" xml:space="preserve">
|
||||
<value>Error code: {0}</value>
|
||||
</data>
|
||||
<data name="General_ErrorMessage" xml:space="preserve">
|
||||
<value>Error message: {0}</value>
|
||||
</data>
|
||||
</root>
|
||||
82
src/ZonyLrcTools.Common/Resources/Messages.resx
Normal file
82
src/ZonyLrcTools.Common/Resources/Messages.resx
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Lyrics Download -->
|
||||
<data name="Lyrics_DownloadStart" xml:space="preserve">
|
||||
<value>开始下载歌词文件数据...</value>
|
||||
</data>
|
||||
<data name="Lyrics_DownloadComplete" xml:space="preserve">
|
||||
<value>歌词数据下载完成,成功: {0} 跳过(纯音乐): {1} 失败: {2}。</value>
|
||||
</data>
|
||||
<data name="Lyrics_SongDownloadSuccess" xml:space="preserve">
|
||||
<value>歌曲名: {0}, 艺术家: {1}, 下载成功。</value>
|
||||
</data>
|
||||
<data name="Lyrics_SongDownloadFailed" xml:space="preserve">
|
||||
<value>歌曲名: {0}, 艺术家: {1}, 下载失败。</value>
|
||||
</data>
|
||||
<!-- Album Download -->
|
||||
<data name="Album_DownloadStart" xml:space="preserve">
|
||||
<value>开始下载专辑封面...</value>
|
||||
</data>
|
||||
<data name="Album_DownloadComplete" xml:space="preserve">
|
||||
<value>专辑封面下载完成,成功: {0} 失败: {1}。</value>
|
||||
</data>
|
||||
<!-- File Scanner -->
|
||||
<data name="Scanner_Start" xml:space="preserve">
|
||||
<value>开始扫描文件夹,请稍等...</value>
|
||||
</data>
|
||||
<data name="Scanner_Complete" xml:space="preserve">
|
||||
<value>扫描完成,共 {0} 个文件。</value>
|
||||
</data>
|
||||
<!-- General -->
|
||||
<data name="General_Starting" xml:space="preserve">
|
||||
<value>正在启动...</value>
|
||||
</data>
|
||||
<data name="General_UnhandledException" xml:space="preserve">
|
||||
<value>出现了未处理的异常。</value>
|
||||
</data>
|
||||
<data name="General_ErrorCode" xml:space="preserve">
|
||||
<value>错误代码: {0}</value>
|
||||
</data>
|
||||
<data name="General_ErrorMessage" xml:space="preserve">
|
||||
<value>错误信息: {0}</value>
|
||||
</data>
|
||||
</root>
|
||||
33
src/ZonyLrcTools.Desktop/App.axaml
Normal file
33
src/ZonyLrcTools.Desktop/App.axaml
Normal file
@@ -0,0 +1,33 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ZonyLrcTools.Desktop.App"
|
||||
RequestedThemeVariant="Default">
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="AppBackgroundBrush">#FFFFFF</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppAccentBrush">#0078D4</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="NavigationBackgroundBrush">#F3F3F3</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="SuccessColorBrush">#107C10</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="ErrorColorBrush">#D13438</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="WarningColorBrush">#FFB900</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="AppBackgroundBrush">#1F1F1F</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppAccentBrush">#60CDFF</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="NavigationBackgroundBrush">#2D2D2D</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="SuccessColorBrush">#6CCB5F</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="ErrorColorBrush">#FF99A4</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="WarningColorBrush">#FCE100</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
</Application>
|
||||
47
src/ZonyLrcTools.Desktop/App.axaml.cs
Normal file
47
src/ZonyLrcTools.Desktop/App.axaml.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Data.Core.Plugins;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZonyLrcTools.Desktop.Services;
|
||||
using ZonyLrcTools.Desktop.ViewModels;
|
||||
using ZonyLrcTools.Desktop.Views;
|
||||
|
||||
namespace ZonyLrcTools.Desktop;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public static ServiceProvider? Services { get; private set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
// Configure DI container BEFORE loading XAML
|
||||
// This is required because LocalizeExtension needs App.Services during XAML loading
|
||||
var services = new ServiceCollection();
|
||||
services.AddDesktopServices();
|
||||
Services = services.BuildServiceProvider();
|
||||
|
||||
// Initialize language settings before loading XAML
|
||||
ServiceCollectionExtensions.InitializeLanguage();
|
||||
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
// Avoid duplicate validations from both Avalonia and CommunityToolkit
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var mainViewModel = Services!.GetRequiredService<MainWindowViewModel>();
|
||||
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = mainViewModel
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Markup.Xaml.MarkupExtensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Infrastructure.Localization;
|
||||
|
||||
/// <summary>
|
||||
/// Avalonia markup extension for localization.
|
||||
/// Usage: Text="{loc:Localize Key=Nav_Home}"
|
||||
/// </summary>
|
||||
public class LocalizeExtension : MarkupExtension
|
||||
{
|
||||
public LocalizeExtension()
|
||||
{
|
||||
}
|
||||
|
||||
public LocalizeExtension(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The resource key to look up.
|
||||
/// </summary>
|
||||
public string? Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default value if the key is not found.
|
||||
/// </summary>
|
||||
public string? Default { get; set; }
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Key))
|
||||
{
|
||||
return Default ?? "[NO_KEY]";
|
||||
}
|
||||
|
||||
var localizer = App.Services?.GetService<IStringLocalizer<Desktop.UIStrings>>();
|
||||
if (localizer == null)
|
||||
{
|
||||
return Default ?? $"[{Key}]";
|
||||
}
|
||||
|
||||
var localizedValue = localizer[Key];
|
||||
if (localizedValue.ResourceNotFound)
|
||||
{
|
||||
return Default ?? $"[{Key}]";
|
||||
}
|
||||
|
||||
return localizedValue.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI localization service for dynamic language switching.
|
||||
/// </summary>
|
||||
public interface IUILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a localized string by key.
|
||||
/// </summary>
|
||||
string this[string key] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localized string by key with format arguments.
|
||||
/// </summary>
|
||||
string this[string key, params object[] args] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current culture name.
|
||||
/// </summary>
|
||||
string CurrentCulture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current UI language.
|
||||
/// </summary>
|
||||
void SetLanguage(string cultureName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the language follows system settings.
|
||||
/// </summary>
|
||||
bool FollowSystem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the language changes.
|
||||
/// </summary>
|
||||
event EventHandler? LanguageChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of UI localization service.
|
||||
/// </summary>
|
||||
public class UILocalizationService : IUILocalizationService
|
||||
{
|
||||
private readonly IStringLocalizer<Desktop.UIStrings> _localizer;
|
||||
private bool _followSystem = true;
|
||||
|
||||
public UILocalizationService(IStringLocalizer<Desktop.UIStrings> localizer)
|
||||
{
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = _localizer[key];
|
||||
return value.ResourceNotFound ? $"[{key}]" : value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public string this[string key, params object[] args]
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = _localizer[key];
|
||||
if (value.ResourceNotFound) return $"[{key}]";
|
||||
return string.Format(value.Value, args);
|
||||
}
|
||||
}
|
||||
|
||||
public string CurrentCulture => System.Globalization.CultureInfo.CurrentUICulture.Name;
|
||||
|
||||
public bool FollowSystem
|
||||
{
|
||||
get => _followSystem;
|
||||
set => _followSystem = value;
|
||||
}
|
||||
|
||||
public event EventHandler? LanguageChanged;
|
||||
|
||||
public void SetLanguage(string cultureName)
|
||||
{
|
||||
var culture = new System.Globalization.CultureInfo(cultureName);
|
||||
System.Globalization.CultureInfo.CurrentCulture = culture;
|
||||
System.Globalization.CultureInfo.CurrentUICulture = culture;
|
||||
LanguageChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
23
src/ZonyLrcTools.Desktop/Program.cs
Normal file
23
src/ZonyLrcTools.Desktop/Program.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Avalonia;
|
||||
|
||||
namespace ZonyLrcTools.Desktop;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
10
src/ZonyLrcTools.Desktop/Resources/UIStrings.cs
Normal file
10
src/ZonyLrcTools.Desktop/Resources/UIStrings.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace ZonyLrcTools.Desktop;
|
||||
|
||||
/// <summary>
|
||||
/// Marker class for UI localization resources.
|
||||
/// This class is used by IStringLocalizer<UIStrings> to locate the resource files.
|
||||
/// The namespace must be the root namespace so that IStringLocalizer looks for Resources/UIStrings.resx
|
||||
/// </summary>
|
||||
public class UIStrings
|
||||
{
|
||||
}
|
||||
245
src/ZonyLrcTools.Desktop/Resources/UIStrings.en-US.resx
Normal file
245
src/ZonyLrcTools.Desktop/Resources/UIStrings.en-US.resx
Normal file
@@ -0,0 +1,245 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Navigation -->
|
||||
<data name="Nav_Home" xml:space="preserve">
|
||||
<value>Home</value>
|
||||
</data>
|
||||
<data name="Nav_LyricsDownload" xml:space="preserve">
|
||||
<value>Lyrics Download</value>
|
||||
</data>
|
||||
<data name="Nav_AlbumDownload" xml:space="preserve">
|
||||
<value>Album Cover</value>
|
||||
</data>
|
||||
<data name="Nav_Settings" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<!-- Home Page -->
|
||||
<data name="Home_Welcome" xml:space="preserve">
|
||||
<value>Welcome to ZonyLrcTools</value>
|
||||
</data>
|
||||
<data name="Home_Description" xml:space="preserve">
|
||||
<value>An open-source lyrics download tool</value>
|
||||
</data>
|
||||
<data name="Home_QuickStart" xml:space="preserve">
|
||||
<value>Quick Start</value>
|
||||
</data>
|
||||
<data name="Home_DownloadLyrics" xml:space="preserve">
|
||||
<value>Download Lyrics</value>
|
||||
</data>
|
||||
<data name="Home_DownloadAlbumCover" xml:space="preserve">
|
||||
<value>Download Album Covers</value>
|
||||
</data>
|
||||
<!-- Lyrics Download Page -->
|
||||
<data name="Lyrics_Title" xml:space="preserve">
|
||||
<value>Lyrics Download</value>
|
||||
</data>
|
||||
<data name="Lyrics_Description" xml:space="preserve">
|
||||
<value>Batch download lyrics for your music files</value>
|
||||
</data>
|
||||
<data name="Lyrics_SelectFolder" xml:space="preserve">
|
||||
<value>Select music folder...</value>
|
||||
</data>
|
||||
<data name="Lyrics_Browse" xml:space="preserve">
|
||||
<value>Browse...</value>
|
||||
</data>
|
||||
<data name="Lyrics_Parallel" xml:space="preserve">
|
||||
<value>Parallel:</value>
|
||||
</data>
|
||||
<data name="Lyrics_StartDownload" xml:space="preserve">
|
||||
<value>Start Download</value>
|
||||
</data>
|
||||
<data name="Lyrics_StopDownload" xml:space="preserve">
|
||||
<value>Stop Download</value>
|
||||
</data>
|
||||
<data name="Lyrics_Progress" xml:space="preserve">
|
||||
<value>Download Progress</value>
|
||||
</data>
|
||||
<data name="Lyrics_Status_Idle" xml:space="preserve">
|
||||
<value>Idle</value>
|
||||
</data>
|
||||
<data name="Lyrics_Status_Scanning" xml:space="preserve">
|
||||
<value>Scanning files...</value>
|
||||
</data>
|
||||
<data name="Lyrics_Status_Downloading" xml:space="preserve">
|
||||
<value>Downloading lyrics...</value>
|
||||
</data>
|
||||
<data name="Lyrics_Status_Complete" xml:space="preserve">
|
||||
<value>Download Complete</value>
|
||||
</data>
|
||||
<data name="Common_Total" xml:space="preserve">
|
||||
<value>Total:</value>
|
||||
</data>
|
||||
<data name="Common_Files" xml:space="preserve">
|
||||
<value>files</value>
|
||||
</data>
|
||||
<data name="Common_Success" xml:space="preserve">
|
||||
<value>Success:</value>
|
||||
</data>
|
||||
<data name="Common_Failed" xml:space="preserve">
|
||||
<value>Failed:</value>
|
||||
</data>
|
||||
<!-- Album Download Page -->
|
||||
<data name="Album_Title" xml:space="preserve">
|
||||
<value>Album Cover Download</value>
|
||||
</data>
|
||||
<data name="Album_Description" xml:space="preserve">
|
||||
<value>Download album artwork for your music collection</value>
|
||||
</data>
|
||||
<data name="Album_SelectFolder" xml:space="preserve">
|
||||
<value>Select music folder...</value>
|
||||
</data>
|
||||
<data name="Album_Browse" xml:space="preserve">
|
||||
<value>Browse...</value>
|
||||
</data>
|
||||
<data name="Album_Parallel" xml:space="preserve">
|
||||
<value>Parallel:</value>
|
||||
</data>
|
||||
<data name="Album_StartDownload" xml:space="preserve">
|
||||
<value>Start Download</value>
|
||||
</data>
|
||||
<data name="Album_StopDownload" xml:space="preserve">
|
||||
<value>Stop Download</value>
|
||||
</data>
|
||||
<!-- Settings Page -->
|
||||
<data name="Settings_Title" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="Settings_Language" xml:space="preserve">
|
||||
<value>Language</value>
|
||||
</data>
|
||||
<data name="Settings_FollowSystem" xml:space="preserve">
|
||||
<value>Follow System</value>
|
||||
</data>
|
||||
<data name="Settings_Chinese" xml:space="preserve">
|
||||
<value>Simplified Chinese</value>
|
||||
</data>
|
||||
<data name="Settings_English" xml:space="preserve">
|
||||
<value>English</value>
|
||||
</data>
|
||||
<data name="Settings_Theme" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeLight" xml:space="preserve">
|
||||
<value>Light</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeDark" xml:space="preserve">
|
||||
<value>Dark</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeSystem" xml:space="preserve">
|
||||
<value>Follow System</value>
|
||||
</data>
|
||||
<data name="Settings_LyricsProvider" xml:space="preserve">
|
||||
<value>Lyrics Provider</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy" xml:space="preserve">
|
||||
<value>Proxy Settings</value>
|
||||
</data>
|
||||
<data name="Settings_ProxyEnable" xml:space="preserve">
|
||||
<value>Enable Proxy</value>
|
||||
</data>
|
||||
<data name="Settings_ProxyAddress" xml:space="preserve">
|
||||
<value>Proxy Address</value>
|
||||
</data>
|
||||
<data name="Settings_ProxyPort" xml:space="preserve">
|
||||
<value>Proxy Port</value>
|
||||
</data>
|
||||
<data name="Settings_Save" xml:space="preserve">
|
||||
<value>Save Settings</value>
|
||||
</data>
|
||||
<data name="Settings_Reset" xml:space="preserve">
|
||||
<value>Reset</value>
|
||||
</data>
|
||||
<!-- DataGrid Columns -->
|
||||
<data name="Column_SongName" xml:space="preserve">
|
||||
<value>Song Name</value>
|
||||
</data>
|
||||
<data name="Column_Artist" xml:space="preserve">
|
||||
<value>Artist</value>
|
||||
</data>
|
||||
<data name="Column_Album" xml:space="preserve">
|
||||
<value>Album</value>
|
||||
</data>
|
||||
<data name="Column_Status" xml:space="preserve">
|
||||
<value>Status</value>
|
||||
</data>
|
||||
<data name="Column_FilePath" xml:space="preserve">
|
||||
<value>File Path</value>
|
||||
</data>
|
||||
<!-- Status Messages -->
|
||||
<data name="Status_Success" xml:space="preserve">
|
||||
<value>Success</value>
|
||||
</data>
|
||||
<data name="Status_Failed" xml:space="preserve">
|
||||
<value>Failed</value>
|
||||
</data>
|
||||
<data name="Status_Skipped" xml:space="preserve">
|
||||
<value>Skipped</value>
|
||||
</data>
|
||||
<data name="Status_Pending" xml:space="preserve">
|
||||
<value>Pending</value>
|
||||
</data>
|
||||
<data name="Status_Processing" xml:space="preserve">
|
||||
<value>Processing</value>
|
||||
</data>
|
||||
<!-- Common -->
|
||||
<data name="Common_OK" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="Common_Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="Common_Yes" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="Common_No" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="Common_Error" xml:space="preserve">
|
||||
<value>Error</value>
|
||||
</data>
|
||||
<data name="Common_Warning" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
<data name="Common_Info" xml:space="preserve">
|
||||
<value>Info</value>
|
||||
</data>
|
||||
</root>
|
||||
245
src/ZonyLrcTools.Desktop/Resources/UIStrings.resx
Normal file
245
src/ZonyLrcTools.Desktop/Resources/UIStrings.resx
Normal file
@@ -0,0 +1,245 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Navigation -->
|
||||
<data name="Nav_Home" xml:space="preserve">
|
||||
<value>首页</value>
|
||||
</data>
|
||||
<data name="Nav_LyricsDownload" xml:space="preserve">
|
||||
<value>歌词下载</value>
|
||||
</data>
|
||||
<data name="Nav_AlbumDownload" xml:space="preserve">
|
||||
<value>专辑封面</value>
|
||||
</data>
|
||||
<data name="Nav_Settings" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
</data>
|
||||
<!-- Home Page -->
|
||||
<data name="Home_Welcome" xml:space="preserve">
|
||||
<value>欢迎使用 ZonyLrcTools</value>
|
||||
</data>
|
||||
<data name="Home_Description" xml:space="preserve">
|
||||
<value>一款开源的歌词下载工具</value>
|
||||
</data>
|
||||
<data name="Home_QuickStart" xml:space="preserve">
|
||||
<value>快速开始</value>
|
||||
</data>
|
||||
<data name="Home_DownloadLyrics" xml:space="preserve">
|
||||
<value>下载歌词</value>
|
||||
</data>
|
||||
<data name="Home_DownloadAlbumCover" xml:space="preserve">
|
||||
<value>下载专辑封面</value>
|
||||
</data>
|
||||
<!-- Lyrics Download Page -->
|
||||
<data name="Lyrics_Title" xml:space="preserve">
|
||||
<value>歌词下载</value>
|
||||
</data>
|
||||
<data name="Lyrics_Description" xml:space="preserve">
|
||||
<value>批量下载音乐文件的歌词</value>
|
||||
</data>
|
||||
<data name="Lyrics_SelectFolder" xml:space="preserve">
|
||||
<value>选择音乐文件夹...</value>
|
||||
</data>
|
||||
<data name="Lyrics_Browse" xml:space="preserve">
|
||||
<value>浏览...</value>
|
||||
</data>
|
||||
<data name="Lyrics_Parallel" xml:space="preserve">
|
||||
<value>并行:</value>
|
||||
</data>
|
||||
<data name="Lyrics_StartDownload" xml:space="preserve">
|
||||
<value>开始下载</value>
|
||||
</data>
|
||||
<data name="Lyrics_StopDownload" xml:space="preserve">
|
||||
<value>停止下载</value>
|
||||
</data>
|
||||
<data name="Lyrics_Progress" xml:space="preserve">
|
||||
<value>下载进度</value>
|
||||
</data>
|
||||
<data name="Lyrics_Status_Idle" xml:space="preserve">
|
||||
<value>等待中</value>
|
||||
</data>
|
||||
<data name="Lyrics_Status_Scanning" xml:space="preserve">
|
||||
<value>正在扫描文件...</value>
|
||||
</data>
|
||||
<data name="Lyrics_Status_Downloading" xml:space="preserve">
|
||||
<value>正在下载歌词...</value>
|
||||
</data>
|
||||
<data name="Lyrics_Status_Complete" xml:space="preserve">
|
||||
<value>下载完成</value>
|
||||
</data>
|
||||
<data name="Common_Total" xml:space="preserve">
|
||||
<value>总计:</value>
|
||||
</data>
|
||||
<data name="Common_Files" xml:space="preserve">
|
||||
<value>个文件</value>
|
||||
</data>
|
||||
<data name="Common_Success" xml:space="preserve">
|
||||
<value>成功:</value>
|
||||
</data>
|
||||
<data name="Common_Failed" xml:space="preserve">
|
||||
<value>失败:</value>
|
||||
</data>
|
||||
<!-- Album Download Page -->
|
||||
<data name="Album_Title" xml:space="preserve">
|
||||
<value>专辑封面下载</value>
|
||||
</data>
|
||||
<data name="Album_Description" xml:space="preserve">
|
||||
<value>批量下载音乐文件的专辑封面</value>
|
||||
</data>
|
||||
<data name="Album_SelectFolder" xml:space="preserve">
|
||||
<value>选择音乐文件夹...</value>
|
||||
</data>
|
||||
<data name="Album_Browse" xml:space="preserve">
|
||||
<value>浏览...</value>
|
||||
</data>
|
||||
<data name="Album_Parallel" xml:space="preserve">
|
||||
<value>并行:</value>
|
||||
</data>
|
||||
<data name="Album_StartDownload" xml:space="preserve">
|
||||
<value>开始下载</value>
|
||||
</data>
|
||||
<data name="Album_StopDownload" xml:space="preserve">
|
||||
<value>停止下载</value>
|
||||
</data>
|
||||
<!-- Settings Page -->
|
||||
<data name="Settings_Title" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
</data>
|
||||
<data name="Settings_Language" xml:space="preserve">
|
||||
<value>语言</value>
|
||||
</data>
|
||||
<data name="Settings_FollowSystem" xml:space="preserve">
|
||||
<value>跟随系统</value>
|
||||
</data>
|
||||
<data name="Settings_Chinese" xml:space="preserve">
|
||||
<value>简体中文</value>
|
||||
</data>
|
||||
<data name="Settings_English" xml:space="preserve">
|
||||
<value>English</value>
|
||||
</data>
|
||||
<data name="Settings_Theme" xml:space="preserve">
|
||||
<value>主题</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeLight" xml:space="preserve">
|
||||
<value>浅色</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeDark" xml:space="preserve">
|
||||
<value>深色</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeSystem" xml:space="preserve">
|
||||
<value>跟随系统</value>
|
||||
</data>
|
||||
<data name="Settings_LyricsProvider" xml:space="preserve">
|
||||
<value>歌词源</value>
|
||||
</data>
|
||||
<data name="Settings_Proxy" xml:space="preserve">
|
||||
<value>代理设置</value>
|
||||
</data>
|
||||
<data name="Settings_ProxyEnable" xml:space="preserve">
|
||||
<value>启用代理</value>
|
||||
</data>
|
||||
<data name="Settings_ProxyAddress" xml:space="preserve">
|
||||
<value>代理地址</value>
|
||||
</data>
|
||||
<data name="Settings_ProxyPort" xml:space="preserve">
|
||||
<value>代理端口</value>
|
||||
</data>
|
||||
<data name="Settings_Save" xml:space="preserve">
|
||||
<value>保存设置</value>
|
||||
</data>
|
||||
<data name="Settings_Reset" xml:space="preserve">
|
||||
<value>重置</value>
|
||||
</data>
|
||||
<!-- DataGrid Columns -->
|
||||
<data name="Column_SongName" xml:space="preserve">
|
||||
<value>歌曲名</value>
|
||||
</data>
|
||||
<data name="Column_Artist" xml:space="preserve">
|
||||
<value>艺术家</value>
|
||||
</data>
|
||||
<data name="Column_Album" xml:space="preserve">
|
||||
<value>专辑</value>
|
||||
</data>
|
||||
<data name="Column_Status" xml:space="preserve">
|
||||
<value>状态</value>
|
||||
</data>
|
||||
<data name="Column_FilePath" xml:space="preserve">
|
||||
<value>文件路径</value>
|
||||
</data>
|
||||
<!-- Status Messages -->
|
||||
<data name="Status_Success" xml:space="preserve">
|
||||
<value>成功</value>
|
||||
</data>
|
||||
<data name="Status_Failed" xml:space="preserve">
|
||||
<value>失败</value>
|
||||
</data>
|
||||
<data name="Status_Skipped" xml:space="preserve">
|
||||
<value>已跳过</value>
|
||||
</data>
|
||||
<data name="Status_Pending" xml:space="preserve">
|
||||
<value>等待中</value>
|
||||
</data>
|
||||
<data name="Status_Processing" xml:space="preserve">
|
||||
<value>处理中</value>
|
||||
</data>
|
||||
<!-- Common -->
|
||||
<data name="Common_OK" xml:space="preserve">
|
||||
<value>确定</value>
|
||||
</data>
|
||||
<data name="Common_Cancel" xml:space="preserve">
|
||||
<value>取消</value>
|
||||
</data>
|
||||
<data name="Common_Yes" xml:space="preserve">
|
||||
<value>是</value>
|
||||
</data>
|
||||
<data name="Common_No" xml:space="preserve">
|
||||
<value>否</value>
|
||||
</data>
|
||||
<data name="Common_Error" xml:space="preserve">
|
||||
<value>错误</value>
|
||||
</data>
|
||||
<data name="Common_Warning" xml:space="preserve">
|
||||
<value>警告</value>
|
||||
</data>
|
||||
<data name="Common_Info" xml:space="preserve">
|
||||
<value>提示</value>
|
||||
</data>
|
||||
</root>
|
||||
67
src/ZonyLrcTools.Desktop/Services/AvaloniaWarpLogger.cs
Normal file
67
src/ZonyLrcTools.Desktop/Services/AvaloniaWarpLogger.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Threading;
|
||||
using ZonyLrcTools.Common.Infrastructure.Logging;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Avalonia implementation of IWarpLogger that supports UI binding.
|
||||
/// </summary>
|
||||
public class AvaloniaWarpLogger : IWarpLogger
|
||||
{
|
||||
private const int MaxLogEntries = 1000;
|
||||
|
||||
public ObservableCollection<LogEntry> LogEntries { get; } = new();
|
||||
|
||||
public event EventHandler<LogEntry>? LogAdded;
|
||||
|
||||
public Task DebugAsync(string message, Exception? exception = null)
|
||||
{
|
||||
return AddLogAsync(LogLevel.Debug, message, exception);
|
||||
}
|
||||
|
||||
public Task InfoAsync(string message, Exception? exception = null)
|
||||
{
|
||||
return AddLogAsync(LogLevel.Info, message, exception);
|
||||
}
|
||||
|
||||
public Task WarnAsync(string message, Exception? exception = null)
|
||||
{
|
||||
return AddLogAsync(LogLevel.Warning, message, exception);
|
||||
}
|
||||
|
||||
public Task ErrorAsync(string message, Exception? exception = null)
|
||||
{
|
||||
return AddLogAsync(LogLevel.Error, message, exception);
|
||||
}
|
||||
|
||||
private async Task AddLogAsync(LogLevel level, string message, Exception? exception)
|
||||
{
|
||||
var entry = new LogEntry(level, message, exception);
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
LogEntries.Add(entry);
|
||||
|
||||
while (LogEntries.Count > MaxLogEntries)
|
||||
{
|
||||
LogEntries.RemoveAt(0);
|
||||
}
|
||||
|
||||
LogAdded?.Invoke(this, entry);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public record LogEntry(LogLevel Level, string Message, Exception? Exception)
|
||||
{
|
||||
public DateTime Timestamp { get; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
60
src/ZonyLrcTools.Desktop/Services/DialogService.cs
Normal file
60
src/ZonyLrcTools.Desktop/Services/DialogService.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Services;
|
||||
|
||||
public class DialogService : IDialogService
|
||||
{
|
||||
private Window? MainWindow =>
|
||||
(Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?
|
||||
.MainWindow;
|
||||
|
||||
public async Task<string?> ShowFolderPickerAsync(string title)
|
||||
{
|
||||
if (MainWindow == null) return null;
|
||||
|
||||
var folders = await MainWindow.StorageProvider.OpenFolderPickerAsync(
|
||||
new FolderPickerOpenOptions
|
||||
{
|
||||
Title = title,
|
||||
AllowMultiple = false
|
||||
});
|
||||
|
||||
return folders.FirstOrDefault()?.Path.LocalPath;
|
||||
}
|
||||
|
||||
public async Task<string?> ShowFilePickerAsync(string title, string[]? filters = null)
|
||||
{
|
||||
if (MainWindow == null) return null;
|
||||
|
||||
var fileTypes = filters?.Select(f => new FilePickerFileType(f)
|
||||
{
|
||||
Patterns = new[] { f }
|
||||
}).ToList();
|
||||
|
||||
var files = await MainWindow.StorageProvider.OpenFilePickerAsync(
|
||||
new FilePickerOpenOptions
|
||||
{
|
||||
Title = title,
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = fileTypes
|
||||
});
|
||||
|
||||
return files.FirstOrDefault()?.Path.LocalPath;
|
||||
}
|
||||
|
||||
public async Task<bool> ShowConfirmDialogAsync(string title, string message)
|
||||
{
|
||||
// TODO: Implement custom confirm dialog
|
||||
await Task.CompletedTask;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task ShowMessageAsync(string title, string message)
|
||||
{
|
||||
// TODO: Implement custom message dialog
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
9
src/ZonyLrcTools.Desktop/Services/IDialogService.cs
Normal file
9
src/ZonyLrcTools.Desktop/Services/IDialogService.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace ZonyLrcTools.Desktop.Services;
|
||||
|
||||
public interface IDialogService
|
||||
{
|
||||
Task<string?> ShowFolderPickerAsync(string title);
|
||||
Task<string?> ShowFilePickerAsync(string title, string[]? filters = null);
|
||||
Task<bool> ShowConfirmDialogAsync(string title, string message);
|
||||
Task ShowMessageAsync(string title, string message);
|
||||
}
|
||||
13
src/ZonyLrcTools.Desktop/Services/INavigationService.cs
Normal file
13
src/ZonyLrcTools.Desktop/Services/INavigationService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Services;
|
||||
|
||||
public interface INavigationService
|
||||
{
|
||||
ViewModelBase? CurrentViewModel { get; }
|
||||
event EventHandler<ViewModelBase>? NavigationChanged;
|
||||
|
||||
TViewModel NavigateTo<TViewModel>() where TViewModel : ViewModelBase;
|
||||
void GoBack();
|
||||
bool CanGoBack { get; }
|
||||
}
|
||||
12
src/ZonyLrcTools.Desktop/Services/IThemeService.cs
Normal file
12
src/ZonyLrcTools.Desktop/Services/IThemeService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Services;
|
||||
|
||||
public interface IThemeService
|
||||
{
|
||||
ThemeVariant CurrentTheme { get; }
|
||||
event EventHandler<ThemeVariant>? ThemeChanged;
|
||||
|
||||
void SetTheme(ThemeVariant theme);
|
||||
void ToggleTheme();
|
||||
}
|
||||
50
src/ZonyLrcTools.Desktop/Services/NavigationService.cs
Normal file
50
src/ZonyLrcTools.Desktop/Services/NavigationService.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Services;
|
||||
|
||||
public class NavigationService : INavigationService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly Stack<ViewModelBase> _navigationStack = new();
|
||||
|
||||
public ViewModelBase? CurrentViewModel => _navigationStack.TryPeek(out var vm) ? vm : null;
|
||||
public bool CanGoBack => _navigationStack.Count > 1;
|
||||
|
||||
public event EventHandler<ViewModelBase>? NavigationChanged;
|
||||
|
||||
public NavigationService(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public TViewModel NavigateTo<TViewModel>() where TViewModel : ViewModelBase
|
||||
{
|
||||
var viewModel = _serviceProvider.GetRequiredService<TViewModel>();
|
||||
|
||||
if (_navigationStack.TryPeek(out var current))
|
||||
{
|
||||
_ = current.OnDeactivatedAsync();
|
||||
}
|
||||
|
||||
_navigationStack.Push(viewModel);
|
||||
_ = viewModel.OnActivatedAsync();
|
||||
|
||||
NavigationChanged?.Invoke(this, viewModel);
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
if (!CanGoBack) return;
|
||||
|
||||
var current = _navigationStack.Pop();
|
||||
_ = current.OnDeactivatedAsync();
|
||||
|
||||
if (_navigationStack.TryPeek(out var previous))
|
||||
{
|
||||
_ = previous.OnActivatedAsync();
|
||||
NavigationChanged?.Invoke(this, previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
||||
using ZonyLrcTools.Common.Infrastructure.Logging;
|
||||
using ZonyLrcTools.Common.Infrastructure.Network;
|
||||
using ZonyLrcTools.Desktop.Infrastructure.Localization;
|
||||
using ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Services;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Register all services for the desktop application.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddDesktopServices(this IServiceCollection services)
|
||||
{
|
||||
// Register Common project's auto dependency injection
|
||||
services.BeginAutoDependencyInject<AvaloniaWarpLogger>();
|
||||
services.BeginAutoDependencyInject<IWarpHttpClient>();
|
||||
|
||||
// Configure Common services
|
||||
services.ConfigureConfiguration();
|
||||
services.ConfigureLocalization();
|
||||
services.ConfigureToolService();
|
||||
|
||||
// Configure Desktop localization
|
||||
// Note: Don't set ResourcesPath here because the embedded resource name is
|
||||
// ZonyLrcTools.Desktop.UIStrings (not ZonyLrcTools.Desktop.Resources.UIStrings)
|
||||
services.AddLocalization();
|
||||
services.AddSingleton<IUILocalizationService, UILocalizationService>();
|
||||
|
||||
// Register GUI-specific IWarpLogger implementation
|
||||
services.AddSingleton<AvaloniaWarpLogger>();
|
||||
services.AddSingleton<IWarpLogger>(sp => sp.GetRequiredService<AvaloniaWarpLogger>());
|
||||
|
||||
// Register navigation and dialog services
|
||||
services.AddSingleton<INavigationService, NavigationService>();
|
||||
services.AddSingleton<IDialogService, DialogService>();
|
||||
services.AddSingleton<IThemeService, ThemeService>();
|
||||
|
||||
// Register ViewModels
|
||||
services.AddTransient<MainWindowViewModel>();
|
||||
services.AddTransient<HomeViewModel>();
|
||||
services.AddTransient<LyricsDownloadViewModel>();
|
||||
services.AddTransient<AlbumDownloadViewModel>();
|
||||
services.AddTransient<SettingsViewModel>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize language settings based on system culture or user preference.
|
||||
/// </summary>
|
||||
public static void InitializeLanguage(string? preferredCulture = null)
|
||||
{
|
||||
CultureInfo culture;
|
||||
|
||||
if (!string.IsNullOrEmpty(preferredCulture))
|
||||
{
|
||||
// Use user's preferred culture
|
||||
culture = new CultureInfo(preferredCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use system culture, fallback to zh-CN if not supported
|
||||
var systemCulture = CultureInfo.CurrentUICulture.Name;
|
||||
culture = systemCulture.StartsWith("zh") ? new CultureInfo("zh-CN") :
|
||||
systemCulture.StartsWith("en") ? new CultureInfo("en-US") :
|
||||
new CultureInfo("zh-CN");
|
||||
}
|
||||
|
||||
CultureInfo.CurrentCulture = culture;
|
||||
CultureInfo.CurrentUICulture = culture;
|
||||
}
|
||||
}
|
||||
28
src/ZonyLrcTools.Desktop/Services/ThemeService.cs
Normal file
28
src/ZonyLrcTools.Desktop/Services/ThemeService.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Services;
|
||||
|
||||
public class ThemeService : IThemeService
|
||||
{
|
||||
public ThemeVariant CurrentTheme => Application.Current?.ActualThemeVariant ?? ThemeVariant.Default;
|
||||
|
||||
public event EventHandler<ThemeVariant>? ThemeChanged;
|
||||
|
||||
public void SetTheme(ThemeVariant theme)
|
||||
{
|
||||
if (Application.Current == null) return;
|
||||
|
||||
Application.Current.RequestedThemeVariant = theme;
|
||||
ThemeChanged?.Invoke(this, theme);
|
||||
}
|
||||
|
||||
public void ToggleTheme()
|
||||
{
|
||||
var newTheme = CurrentTheme == ThemeVariant.Dark
|
||||
? ThemeVariant.Light
|
||||
: ThemeVariant.Dark;
|
||||
|
||||
SetTheme(newTheme);
|
||||
}
|
||||
}
|
||||
137
src/ZonyLrcTools.Desktop/ViewModels/AlbumDownloadViewModel.cs
Normal file
137
src/ZonyLrcTools.Desktop/ViewModels/AlbumDownloadViewModel.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ZonyLrcTools.Common.Album;
|
||||
using ZonyLrcTools.Desktop.Infrastructure.Localization;
|
||||
using ZonyLrcTools.Desktop.Services;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
public partial class AlbumDownloadViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IAlbumDownloader _albumDownloader;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IUILocalizationService? _localization;
|
||||
|
||||
private CancellationTokenSource? _downloadCts;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _selectedFolderPath;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _parallelCount = 2;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _totalCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _completedCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _failedCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _progressPercentage;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanStartDownload))]
|
||||
private bool _isDownloading;
|
||||
|
||||
// Localized strings
|
||||
public string AlbumTitle => _localization?["Album_Title"] ?? "Album Cover Download";
|
||||
public string AlbumDescription => _localization?["Album_Description"] ?? "Download album artwork for your music collection";
|
||||
public string AlbumSelectFolder => _localization?["Album_SelectFolder"] ?? "Select music folder...";
|
||||
public string AlbumBrowse => _localization?["Album_Browse"] ?? "Browse...";
|
||||
public string AlbumParallel => _localization?["Album_Parallel"] ?? "Parallel:";
|
||||
public string AlbumStartDownload => _localization?["Album_StartDownload"] ?? "Start Download";
|
||||
public string AlbumStopDownload => _localization?["Album_StopDownload"] ?? "Stop Download";
|
||||
public string CommonTotal => _localization?["Common_Total"] ?? "Total:";
|
||||
public string CommonFiles => _localization?["Common_Files"] ?? "files";
|
||||
public string CommonSuccess => _localization?["Common_Success"] ?? "Success:";
|
||||
public string CommonFailed => _localization?["Common_Failed"] ?? "Failed:";
|
||||
public string ColumnSongName => _localization?["Column_SongName"] ?? "Song Name";
|
||||
public string ColumnArtist => _localization?["Column_Artist"] ?? "Artist";
|
||||
public string ColumnStatus => _localization?["Column_Status"] ?? "Status";
|
||||
|
||||
public bool CanStartDownload => !IsDownloading && !string.IsNullOrEmpty(SelectedFolderPath);
|
||||
|
||||
public ObservableCollection<MusicFileViewModel> MusicFiles { get; } = new();
|
||||
|
||||
public AlbumDownloadViewModel(
|
||||
IAlbumDownloader albumDownloader,
|
||||
IDialogService dialogService,
|
||||
IUILocalizationService? localization = null)
|
||||
{
|
||||
_albumDownloader = albumDownloader;
|
||||
_dialogService = dialogService;
|
||||
_localization = localization;
|
||||
|
||||
if (_localization != null)
|
||||
{
|
||||
_localization.LanguageChanged += OnLanguageChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLanguageChanged(object? sender, EventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(AlbumTitle));
|
||||
OnPropertyChanged(nameof(AlbumDescription));
|
||||
OnPropertyChanged(nameof(AlbumSelectFolder));
|
||||
OnPropertyChanged(nameof(AlbumBrowse));
|
||||
OnPropertyChanged(nameof(AlbumParallel));
|
||||
OnPropertyChanged(nameof(AlbumStartDownload));
|
||||
OnPropertyChanged(nameof(AlbumStopDownload));
|
||||
OnPropertyChanged(nameof(CommonTotal));
|
||||
OnPropertyChanged(nameof(CommonFiles));
|
||||
OnPropertyChanged(nameof(CommonSuccess));
|
||||
OnPropertyChanged(nameof(CommonFailed));
|
||||
OnPropertyChanged(nameof(ColumnSongName));
|
||||
OnPropertyChanged(nameof(ColumnArtist));
|
||||
OnPropertyChanged(nameof(ColumnStatus));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SelectFolderAsync()
|
||||
{
|
||||
var folder = await _dialogService.ShowFolderPickerAsync("Select Music Folder");
|
||||
if (string.IsNullOrEmpty(folder)) return;
|
||||
|
||||
SelectedFolderPath = folder;
|
||||
OnPropertyChanged(nameof(CanStartDownload));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task StartDownloadAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedFolderPath)) return;
|
||||
|
||||
IsDownloading = true;
|
||||
CompletedCount = 0;
|
||||
FailedCount = 0;
|
||||
ProgressPercentage = 0;
|
||||
|
||||
_downloadCts = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: Implement actual download logic using _albumDownloader
|
||||
await Task.Delay(1000, _downloadCts.Token); // Placeholder
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// User cancelled
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsDownloading = false;
|
||||
_downloadCts?.Dispose();
|
||||
_downloadCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CancelDownload()
|
||||
{
|
||||
_downloadCts?.Cancel();
|
||||
}
|
||||
}
|
||||
15
src/ZonyLrcTools.Desktop/ViewModels/HomeViewModel.cs
Normal file
15
src/ZonyLrcTools.Desktop/ViewModels/HomeViewModel.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
public partial class HomeViewModel : ViewModelBase
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string _welcomeMessage = "Welcome to ZonyLrcTools X";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _description = "A cross-platform tool for downloading lyrics and album covers for your music collection.";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _version = "4.0.3";
|
||||
}
|
||||
164
src/ZonyLrcTools.Desktop/ViewModels/LyricsDownloadViewModel.cs
Normal file
164
src/ZonyLrcTools.Desktop/ViewModels/LyricsDownloadViewModel.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ZonyLrcTools.Common.Lyrics;
|
||||
using ZonyLrcTools.Desktop.Infrastructure.Localization;
|
||||
using ZonyLrcTools.Desktop.Services;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
public partial class LyricsDownloadViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ILyricsDownloader _lyricsDownloader;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IUILocalizationService? _localization;
|
||||
|
||||
private CancellationTokenSource? _downloadCts;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _selectedFolderPath;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _parallelCount = 2;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _totalCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _completedCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _failedCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _progressPercentage;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(StartDownloadCommand))]
|
||||
[NotifyPropertyChangedFor(nameof(CanStartDownload))]
|
||||
private bool _isDownloading;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _currentProcessingFile;
|
||||
|
||||
// Localized strings
|
||||
public string LyricsTitle => _localization?["Lyrics_Title"] ?? "Lyrics Download";
|
||||
public string LyricsDescription => _localization?["Lyrics_Description"] ?? "Batch download lyrics for your music files";
|
||||
public string LyricsSelectFolder => _localization?["Lyrics_SelectFolder"] ?? "Select music folder...";
|
||||
public string LyricsBrowse => _localization?["Lyrics_Browse"] ?? "Browse...";
|
||||
public string LyricsParallel => _localization?["Lyrics_Parallel"] ?? "Parallel:";
|
||||
public string LyricsStartDownload => _localization?["Lyrics_StartDownload"] ?? "Start Download";
|
||||
public string LyricsStopDownload => _localization?["Lyrics_StopDownload"] ?? "Stop Download";
|
||||
public string CommonTotal => _localization?["Common_Total"] ?? "Total:";
|
||||
public string CommonFiles => _localization?["Common_Files"] ?? "files";
|
||||
public string CommonSuccess => _localization?["Common_Success"] ?? "Success:";
|
||||
public string CommonFailed => _localization?["Common_Failed"] ?? "Failed:";
|
||||
public string ColumnSongName => _localization?["Column_SongName"] ?? "Song Name";
|
||||
public string ColumnArtist => _localization?["Column_Artist"] ?? "Artist";
|
||||
public string ColumnFilePath => _localization?["Column_FilePath"] ?? "File Path";
|
||||
public string ColumnStatus => _localization?["Column_Status"] ?? "Status";
|
||||
|
||||
public bool CanStartDownload => !IsDownloading && !string.IsNullOrEmpty(SelectedFolderPath);
|
||||
|
||||
public ObservableCollection<MusicFileViewModel> MusicFiles { get; } = new();
|
||||
|
||||
public LyricsDownloadViewModel(
|
||||
ILyricsDownloader lyricsDownloader,
|
||||
IDialogService dialogService,
|
||||
IUILocalizationService? localization = null)
|
||||
{
|
||||
_lyricsDownloader = lyricsDownloader;
|
||||
_dialogService = dialogService;
|
||||
_localization = localization;
|
||||
|
||||
if (_localization != null)
|
||||
{
|
||||
_localization.LanguageChanged += OnLanguageChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLanguageChanged(object? sender, EventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(LyricsTitle));
|
||||
OnPropertyChanged(nameof(LyricsDescription));
|
||||
OnPropertyChanged(nameof(LyricsSelectFolder));
|
||||
OnPropertyChanged(nameof(LyricsBrowse));
|
||||
OnPropertyChanged(nameof(LyricsParallel));
|
||||
OnPropertyChanged(nameof(LyricsStartDownload));
|
||||
OnPropertyChanged(nameof(LyricsStopDownload));
|
||||
OnPropertyChanged(nameof(CommonTotal));
|
||||
OnPropertyChanged(nameof(CommonFiles));
|
||||
OnPropertyChanged(nameof(CommonSuccess));
|
||||
OnPropertyChanged(nameof(CommonFailed));
|
||||
OnPropertyChanged(nameof(ColumnSongName));
|
||||
OnPropertyChanged(nameof(ColumnArtist));
|
||||
OnPropertyChanged(nameof(ColumnFilePath));
|
||||
OnPropertyChanged(nameof(ColumnStatus));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SelectFolderAsync()
|
||||
{
|
||||
var folder = await _dialogService.ShowFolderPickerAsync("Select Music Folder");
|
||||
if (string.IsNullOrEmpty(folder)) return;
|
||||
|
||||
SelectedFolderPath = folder;
|
||||
OnPropertyChanged(nameof(CanStartDownload));
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanStartDownload))]
|
||||
private async Task StartDownloadAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedFolderPath)) return;
|
||||
|
||||
IsDownloading = true;
|
||||
CompletedCount = 0;
|
||||
FailedCount = 0;
|
||||
ProgressPercentage = 0;
|
||||
|
||||
_downloadCts = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: Implement actual download logic using _lyricsDownloader
|
||||
await Task.Delay(1000, _downloadCts.Token); // Placeholder
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// User cancelled
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsDownloading = false;
|
||||
_downloadCts?.Dispose();
|
||||
_downloadCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CancelDownload()
|
||||
{
|
||||
_downloadCts?.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class MusicFileViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string _filePath = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _name = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _artist = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isProcessed;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isSuccessful;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _statusMessage;
|
||||
}
|
||||
72
src/ZonyLrcTools.Desktop/ViewModels/MainWindowViewModel.cs
Normal file
72
src/ZonyLrcTools.Desktop/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ZonyLrcTools.Desktop.Infrastructure.Localization;
|
||||
using ZonyLrcTools.Desktop.Services;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IThemeService _themeService;
|
||||
private readonly IUILocalizationService _localization;
|
||||
|
||||
[ObservableProperty]
|
||||
private ViewModelBase? _currentPage;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedNavigationIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ThemeButtonText))]
|
||||
private bool _isDarkTheme;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _title = "ZonyLrcTools X";
|
||||
|
||||
// Navigation localized strings
|
||||
public string NavHome => _localization["Nav_Home"];
|
||||
public string NavLyricsDownload => _localization["Nav_LyricsDownload"];
|
||||
public string NavAlbumDownload => _localization["Nav_AlbumDownload"];
|
||||
public string NavSettings => _localization["Nav_Settings"];
|
||||
public string HomeDescription => _localization["Home_Description"];
|
||||
|
||||
public string ThemeButtonText => IsDarkTheme
|
||||
? _localization["Settings_ThemeLight"]
|
||||
: _localization["Settings_ThemeDark"];
|
||||
|
||||
public MainWindowViewModel(
|
||||
IThemeService themeService,
|
||||
IUILocalizationService localization,
|
||||
HomeViewModel homeViewModel)
|
||||
{
|
||||
_themeService = themeService;
|
||||
_localization = localization;
|
||||
|
||||
// Subscribe to language changes
|
||||
_localization.LanguageChanged += OnLanguageChanged;
|
||||
|
||||
// Set initial page
|
||||
CurrentPage = homeViewModel;
|
||||
SelectedNavigationIndex = 0;
|
||||
}
|
||||
|
||||
private void OnLanguageChanged(object? sender, EventArgs e)
|
||||
{
|
||||
// Notify all localized properties have changed
|
||||
OnPropertyChanged(nameof(NavHome));
|
||||
OnPropertyChanged(nameof(NavLyricsDownload));
|
||||
OnPropertyChanged(nameof(NavAlbumDownload));
|
||||
OnPropertyChanged(nameof(NavSettings));
|
||||
OnPropertyChanged(nameof(HomeDescription));
|
||||
OnPropertyChanged(nameof(ThemeButtonText));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleTheme()
|
||||
{
|
||||
IsDarkTheme = !IsDarkTheme;
|
||||
_themeService.SetTheme(IsDarkTheme
|
||||
? Avalonia.Styling.ThemeVariant.Dark
|
||||
: Avalonia.Styling.ThemeVariant.Light);
|
||||
}
|
||||
}
|
||||
253
src/ZonyLrcTools.Desktop/ViewModels/SettingsViewModel.cs
Normal file
253
src/ZonyLrcTools.Desktop/ViewModels/SettingsViewModel.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Extensions.Options;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using ZonyLrcTools.Common.Configuration;
|
||||
using ZonyLrcTools.Desktop.Infrastructure.Localization;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
public partial class SettingsViewModel : ViewModelBase
|
||||
{
|
||||
private readonly GlobalOptions _globalOptions;
|
||||
private readonly IUILocalizationService? _localizationService;
|
||||
private bool _isLoading = true; // Prevent saving during initial load
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _followSystemLanguage = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isChineseSelected = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEnglishSelected;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isProxyEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _proxyIp = "127.0.0.1";
|
||||
|
||||
[ObservableProperty]
|
||||
private int _proxyPort = 7890;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isTranslationEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isOneLineMode;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _skipExistingLyrics;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _selectedEncoding = "utf-8";
|
||||
|
||||
// Localized strings
|
||||
public string SettingsTitle => _localizationService?["Settings_Title"] ?? "Settings";
|
||||
public string SettingsLanguage => _localizationService?["Settings_Language"] ?? "Language";
|
||||
public string SettingsFollowSystem => _localizationService?["Settings_FollowSystem"] ?? "Follow System";
|
||||
public string SettingsChinese => _localizationService?["Settings_Chinese"] ?? "Chinese";
|
||||
public string SettingsEnglish => _localizationService?["Settings_English"] ?? "English";
|
||||
public string SettingsProxy => _localizationService?["Settings_Proxy"] ?? "Proxy";
|
||||
public string SettingsProxyEnable => _localizationService?["Settings_ProxyEnable"] ?? "Enable Proxy";
|
||||
public string SettingsProxyAddress => _localizationService?["Settings_ProxyAddress"] ?? "Proxy Address";
|
||||
public string SettingsLyricsProvider => _localizationService?["Settings_LyricsProvider"] ?? "Lyrics Provider";
|
||||
public string SettingsReset => _localizationService?["Settings_Reset"] ?? "Reset";
|
||||
|
||||
public ObservableCollection<string> AvailableEncodings { get; } = new()
|
||||
{
|
||||
"utf-8",
|
||||
"utf-8-bom",
|
||||
"gb2312",
|
||||
"gbk"
|
||||
};
|
||||
|
||||
public ObservableCollection<LyricsProviderSettingViewModel> LyricsProviders { get; } = new();
|
||||
|
||||
public SettingsViewModel(IOptions<GlobalOptions> options, IUILocalizationService? localizationService = null)
|
||||
{
|
||||
_globalOptions = options.Value;
|
||||
_localizationService = localizationService;
|
||||
|
||||
// Subscribe to language changes
|
||||
if (_localizationService != null)
|
||||
{
|
||||
_localizationService.LanguageChanged += OnLanguageChanged;
|
||||
}
|
||||
|
||||
LoadSettings();
|
||||
LoadLanguageSettings();
|
||||
_isLoading = false; // Allow saving after initial load
|
||||
}
|
||||
|
||||
private void OnLanguageChanged(object? sender, EventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(SettingsTitle));
|
||||
OnPropertyChanged(nameof(SettingsLanguage));
|
||||
OnPropertyChanged(nameof(SettingsFollowSystem));
|
||||
OnPropertyChanged(nameof(SettingsChinese));
|
||||
OnPropertyChanged(nameof(SettingsEnglish));
|
||||
OnPropertyChanged(nameof(SettingsProxy));
|
||||
OnPropertyChanged(nameof(SettingsProxyEnable));
|
||||
OnPropertyChanged(nameof(SettingsProxyAddress));
|
||||
OnPropertyChanged(nameof(SettingsLyricsProvider));
|
||||
OnPropertyChanged(nameof(SettingsReset));
|
||||
}
|
||||
|
||||
private void LoadLanguageSettings()
|
||||
{
|
||||
var currentCulture = CultureInfo.CurrentUICulture.Name;
|
||||
IsChineseSelected = currentCulture.StartsWith("zh");
|
||||
IsEnglishSelected = currentCulture.StartsWith("en");
|
||||
}
|
||||
|
||||
partial void OnFollowSystemLanguageChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
// Auto-detect system language
|
||||
var systemCulture = CultureInfo.InstalledUICulture.Name;
|
||||
ApplyLanguage(systemCulture.StartsWith("zh") ? "zh-CN" : "en-US");
|
||||
}
|
||||
SaveSettingsIfNotLoading();
|
||||
}
|
||||
|
||||
partial void OnIsChineseSelectedChanged(bool value)
|
||||
{
|
||||
if (value && !FollowSystemLanguage)
|
||||
{
|
||||
ApplyLanguage("zh-CN");
|
||||
}
|
||||
SaveSettingsIfNotLoading();
|
||||
}
|
||||
|
||||
partial void OnIsEnglishSelectedChanged(bool value)
|
||||
{
|
||||
if (value && !FollowSystemLanguage)
|
||||
{
|
||||
ApplyLanguage("en-US");
|
||||
}
|
||||
SaveSettingsIfNotLoading();
|
||||
}
|
||||
|
||||
partial void OnIsProxyEnabledChanged(bool value) => SaveSettingsIfNotLoading();
|
||||
partial void OnProxyIpChanged(string value) => SaveSettingsIfNotLoading();
|
||||
partial void OnProxyPortChanged(int value) => SaveSettingsIfNotLoading();
|
||||
partial void OnIsTranslationEnabledChanged(bool value) => SaveSettingsIfNotLoading();
|
||||
partial void OnIsOneLineModeChanged(bool value) => SaveSettingsIfNotLoading();
|
||||
partial void OnSkipExistingLyricsChanged(bool value) => SaveSettingsIfNotLoading();
|
||||
partial void OnSelectedEncodingChanged(string value) => SaveSettingsIfNotLoading();
|
||||
|
||||
private void ApplyLanguage(string cultureName)
|
||||
{
|
||||
_localizationService?.SetLanguage(cultureName);
|
||||
}
|
||||
|
||||
private void SaveSettingsIfNotLoading()
|
||||
{
|
||||
if (!_isLoading)
|
||||
{
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
// Load network settings
|
||||
IsProxyEnabled = _globalOptions.NetworkOptions?.IsEnable ?? false;
|
||||
ProxyIp = _globalOptions.NetworkOptions?.Ip ?? "127.0.0.1";
|
||||
ProxyPort = _globalOptions.NetworkOptions?.Port ?? 7890;
|
||||
|
||||
// Load lyrics settings
|
||||
var lyricsConfig = _globalOptions.Provider?.Lyric?.Config;
|
||||
if (lyricsConfig != null)
|
||||
{
|
||||
IsTranslationEnabled = lyricsConfig.IsEnableTranslation;
|
||||
IsOneLineMode = lyricsConfig.IsOneLine;
|
||||
SkipExistingLyrics = lyricsConfig.IsSkipExistLyricFiles;
|
||||
SelectedEncoding = lyricsConfig.FileEncoding ?? "utf-8";
|
||||
}
|
||||
|
||||
// Load lyrics providers
|
||||
var providers = _globalOptions.Provider?.Lyric?.Plugin;
|
||||
if (providers != null)
|
||||
{
|
||||
foreach (var provider in providers.OrderBy(p => p.Priority))
|
||||
{
|
||||
LyricsProviders.Add(new LyricsProviderSettingViewModel
|
||||
{
|
||||
Name = provider.Name ?? "Unknown",
|
||||
Priority = provider.Priority,
|
||||
IsEnabled = provider.Priority != -1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSettingsToFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Update GlobalOptions with current values
|
||||
if (_globalOptions.NetworkOptions != null)
|
||||
{
|
||||
_globalOptions.NetworkOptions.IsEnable = IsProxyEnabled;
|
||||
_globalOptions.NetworkOptions.Ip = ProxyIp;
|
||||
_globalOptions.NetworkOptions.Port = ProxyPort;
|
||||
}
|
||||
|
||||
if (_globalOptions.Provider?.Lyric?.Config != null)
|
||||
{
|
||||
_globalOptions.Provider.Lyric.Config.IsEnableTranslation = IsTranslationEnabled;
|
||||
_globalOptions.Provider.Lyric.Config.IsOneLine = IsOneLineMode;
|
||||
_globalOptions.Provider.Lyric.Config.IsSkipExistLyricFiles = SkipExistingLyrics;
|
||||
_globalOptions.Provider.Lyric.Config.FileEncoding = SelectedEncoding;
|
||||
}
|
||||
|
||||
// Serialize and save to file
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
var yaml = serializer.Serialize(_globalOptions);
|
||||
var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.yaml");
|
||||
File.WriteAllText(configPath, yaml);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error but don't crash - settings save is best effort
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to save settings: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ResetToDefaults()
|
||||
{
|
||||
_isLoading = true; // Prevent multiple saves during reset
|
||||
IsProxyEnabled = false;
|
||||
ProxyIp = "127.0.0.1";
|
||||
ProxyPort = 7890;
|
||||
IsTranslationEnabled = false;
|
||||
IsOneLineMode = false;
|
||||
SkipExistingLyrics = true;
|
||||
SelectedEncoding = "utf-8";
|
||||
_isLoading = false;
|
||||
SaveSettingsToFile(); // Save once after all resets
|
||||
}
|
||||
}
|
||||
|
||||
public partial class LyricsProviderSettingViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string _name = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _priority;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEnabled;
|
||||
}
|
||||
28
src/ZonyLrcTools.Desktop/ViewModels/ViewModelBase.cs
Normal file
28
src/ZonyLrcTools.Desktop/ViewModels/ViewModelBase.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all ViewModels.
|
||||
/// </summary>
|
||||
public abstract partial class ViewModelBase : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsNotBusy))]
|
||||
private bool _isBusy;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _busyMessage;
|
||||
|
||||
public bool IsNotBusy => !IsBusy;
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ViewModel is activated (navigated to).
|
||||
/// </summary>
|
||||
public virtual Task OnActivatedAsync() => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Called when the ViewModel is deactivated (navigated away).
|
||||
/// </summary>
|
||||
public virtual Task OnDeactivatedAsync() => Task.CompletedTask;
|
||||
}
|
||||
81
src/ZonyLrcTools.Desktop/Views/MainWindow.axaml
Normal file
81
src/ZonyLrcTools.Desktop/Views/MainWindow.axaml
Normal file
@@ -0,0 +1,81 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ZonyLrcTools.Desktop.ViewModels"
|
||||
xmlns:pages="using:ZonyLrcTools.Desktop.Views.Pages"
|
||||
xmlns:loc="using:ZonyLrcTools.Desktop.Infrastructure.Localization"
|
||||
x:Class="ZonyLrcTools.Desktop.Views.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Title="{Binding Title}"
|
||||
Width="1200" Height="800"
|
||||
MinWidth="900" MinHeight="600"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
|
||||
<Grid ColumnDefinitions="220,*">
|
||||
<!-- Left Navigation Panel -->
|
||||
<Border Grid.Column="0"
|
||||
Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
Padding="8">
|
||||
<DockPanel>
|
||||
<!-- App Title -->
|
||||
<StackPanel DockPanel.Dock="Top" Margin="8,16,8,24">
|
||||
<TextBlock Text="ZonyLrcTools"
|
||||
FontSize="24"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="{Binding HomeDescription}"
|
||||
FontSize="12"
|
||||
Opacity="0.7"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,4,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Theme Toggle -->
|
||||
<Button DockPanel.Dock="Bottom"
|
||||
Content="{Binding ThemeButtonText}"
|
||||
Command="{Binding ToggleThemeCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Margin="8" />
|
||||
|
||||
<!-- Navigation Menu -->
|
||||
<ListBox SelectedIndex="{Binding SelectedNavigationIndex}"
|
||||
Margin="0,8"
|
||||
SelectionChanged="OnNavigationSelectionChanged">
|
||||
<ListBoxItem>
|
||||
<TextBlock Text="{Binding NavHome}" VerticalAlignment="Center" />
|
||||
</ListBoxItem>
|
||||
<ListBoxItem>
|
||||
<TextBlock Text="{Binding NavLyricsDownload}" VerticalAlignment="Center" />
|
||||
</ListBoxItem>
|
||||
<ListBoxItem>
|
||||
<TextBlock Text="{Binding NavAlbumDownload}" VerticalAlignment="Center" />
|
||||
</ListBoxItem>
|
||||
<ListBoxItem>
|
||||
<TextBlock Text="{Binding NavSettings}" VerticalAlignment="Center" />
|
||||
</ListBoxItem>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Right Content Area -->
|
||||
<Border Grid.Column="1" Padding="24">
|
||||
<ContentControl Content="{Binding CurrentPage}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:HomeViewModel">
|
||||
<pages:HomePage />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:LyricsDownloadViewModel">
|
||||
<pages:LyricsDownloadPage />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:AlbumDownloadViewModel">
|
||||
<pages:AlbumDownloadPage />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:SettingsViewModel">
|
||||
<pages:SettingsPage />
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
38
src/ZonyLrcTools.Desktop/Views/MainWindow.axaml.cs
Normal file
38
src/ZonyLrcTools.Desktop/Views/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Avalonia.Controls;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZonyLrcTools.Desktop.ViewModels;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnNavigationSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (DataContext is not MainWindowViewModel viewModel) return;
|
||||
if (sender is not ListBox listBox) return;
|
||||
|
||||
var services = App.Services;
|
||||
if (services == null) return;
|
||||
|
||||
switch (listBox.SelectedIndex)
|
||||
{
|
||||
case 0:
|
||||
viewModel.CurrentPage = services.GetRequiredService<HomeViewModel>();
|
||||
break;
|
||||
case 1:
|
||||
viewModel.CurrentPage = services.GetRequiredService<LyricsDownloadViewModel>();
|
||||
break;
|
||||
case 2:
|
||||
viewModel.CurrentPage = services.GetRequiredService<AlbumDownloadViewModel>();
|
||||
break;
|
||||
case 3:
|
||||
viewModel.CurrentPage = services.GetRequiredService<SettingsViewModel>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
src/ZonyLrcTools.Desktop/Views/Pages/AlbumDownloadPage.axaml
Normal file
142
src/ZonyLrcTools.Desktop/Views/Pages/AlbumDownloadPage.axaml
Normal file
@@ -0,0 +1,142 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ZonyLrcTools.Desktop.ViewModels"
|
||||
x:Class="ZonyLrcTools.Desktop.Views.Pages.AlbumDownloadPage"
|
||||
x:DataType="vm:AlbumDownloadViewModel">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<!-- Title -->
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,24">
|
||||
<TextBlock Text="{Binding AlbumTitle}"
|
||||
FontSize="28"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding AlbumDescription}"
|
||||
Opacity="0.7"
|
||||
Margin="0,4,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Controls -->
|
||||
<Border Grid.Row="1"
|
||||
Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="16"
|
||||
Margin="0,0,0,16">
|
||||
<StackPanel Spacing="16">
|
||||
<!-- Row 1: Folder Selection -->
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding SelectedFolderPath}"
|
||||
Watermark="{Binding AlbumSelectFolder}"
|
||||
IsReadOnly="True"
|
||||
Margin="0,0,12,0" />
|
||||
<Button Grid.Column="1"
|
||||
Content="{Binding AlbumBrowse}"
|
||||
Command="{Binding SelectFolderCommand}"
|
||||
MinWidth="80" />
|
||||
</Grid>
|
||||
|
||||
<!-- Row 2: Options and Actions -->
|
||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto">
|
||||
<!-- Parallel Count -->
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{Binding AlbumParallel}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding ParallelCount}"
|
||||
Width="60"
|
||||
TextAlignment="Center"
|
||||
VerticalContentAlignment="Center" />
|
||||
|
||||
<!-- Spacer -->
|
||||
<Panel Grid.Column="2" />
|
||||
|
||||
<!-- Download Buttons -->
|
||||
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="8">
|
||||
<Button Command="{Binding StartDownloadCommand}"
|
||||
Content="{Binding AlbumStartDownload}"
|
||||
IsVisible="{Binding !IsDownloading}"
|
||||
Classes="accent"
|
||||
MinWidth="100" />
|
||||
<Button Command="{Binding CancelDownloadCommand}"
|
||||
Content="{Binding AlbumStopDownload}"
|
||||
IsVisible="{Binding IsDownloading}"
|
||||
MinWidth="100" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Progress Bar (visible during download) -->
|
||||
<StackPanel IsVisible="{Binding IsDownloading}" Spacing="8">
|
||||
<ProgressBar Value="{Binding ProgressPercentage}"
|
||||
Maximum="100"
|
||||
Height="6" />
|
||||
<TextBlock HorizontalAlignment="Center" FontSize="12" Opacity="0.8">
|
||||
<Run Text="{Binding CompletedCount}" />
|
||||
<Run Text=" / " />
|
||||
<Run Text="{Binding TotalCount}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- File List -->
|
||||
<Border Grid.Row="2"
|
||||
Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="1">
|
||||
<DataGrid ItemsSource="{Binding MusicFiles}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal"
|
||||
BorderThickness="0">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="{Binding ColumnSongName}"
|
||||
Binding="{Binding Name}"
|
||||
Width="*" />
|
||||
<DataGridTextColumn Header="{Binding ColumnArtist}"
|
||||
Binding="{Binding Artist}"
|
||||
Width="200" />
|
||||
<DataGridTextColumn Header="{Binding ColumnStatus}"
|
||||
Binding="{Binding StatusMessage}"
|
||||
Width="120" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<Border Grid.Row="3"
|
||||
Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
Padding="16,12"
|
||||
Margin="0,16,0,0"
|
||||
CornerRadius="8">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<!-- Total -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock Text="{Binding CommonTotal}" Opacity="0.8" />
|
||||
<TextBlock Text="{Binding TotalCount}" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding CommonFiles}" Opacity="0.8" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Spacer -->
|
||||
<Panel Grid.Column="1" />
|
||||
|
||||
<!-- Success -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4" Margin="0,0,24,0">
|
||||
<TextBlock Text="{Binding CommonSuccess}" Opacity="0.8" />
|
||||
<TextBlock Text="{Binding CompletedCount}"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#4CAF50" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Failed -->
|
||||
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock Text="{Binding CommonFailed}" Opacity="0.8" />
|
||||
<TextBlock Text="{Binding FailedCount}"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#F44336" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Views.Pages;
|
||||
|
||||
public partial class AlbumDownloadPage : UserControl
|
||||
{
|
||||
public AlbumDownloadPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
97
src/ZonyLrcTools.Desktop/Views/Pages/HomePage.axaml
Normal file
97
src/ZonyLrcTools.Desktop/Views/Pages/HomePage.axaml
Normal file
@@ -0,0 +1,97 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ZonyLrcTools.Desktop.ViewModels"
|
||||
x:Class="ZonyLrcTools.Desktop.Views.Pages.HomePage"
|
||||
x:DataType="vm:HomeViewModel">
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel Spacing="24">
|
||||
<!-- Header -->
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding WelcomeMessage}"
|
||||
FontSize="32"
|
||||
FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding Description}"
|
||||
FontSize="14"
|
||||
Opacity="0.7"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<Border Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="24">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="Quick Actions"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<WrapPanel Orientation="Horizontal">
|
||||
<Button Margin="0,0,16,16"
|
||||
Padding="24,16"
|
||||
CornerRadius="8">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Download Lyrics"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="Batch download lyrics for your music"
|
||||
FontSize="12"
|
||||
Opacity="0.7" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Margin="0,0,16,16"
|
||||
Padding="24,16"
|
||||
CornerRadius="8">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Download Album Covers"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="Get album artwork for your collection"
|
||||
FontSize="12"
|
||||
Opacity="0.7" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Features -->
|
||||
<Border Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="24">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="Features"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<ItemsControl>
|
||||
<ItemsControl.Items>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,4">
|
||||
<TextBlock Text="*" FontWeight="Bold" Foreground="{DynamicResource AppAccentBrush}" />
|
||||
<TextBlock Text="Multiple lyrics sources: NetEase, QQ Music, KuGou, KuWo" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,4">
|
||||
<TextBlock Text="*" FontWeight="Bold" Foreground="{DynamicResource AppAccentBrush}" />
|
||||
<TextBlock Text="Batch processing with parallel downloads" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,4">
|
||||
<TextBlock Text="*" FontWeight="Bold" Foreground="{DynamicResource AppAccentBrush}" />
|
||||
<TextBlock Text="Support for translation lyrics" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,4">
|
||||
<TextBlock Text="*" FontWeight="Bold" Foreground="{DynamicResource AppAccentBrush}" />
|
||||
<TextBlock Text="Cross-platform: Windows, macOS, Linux" />
|
||||
</StackPanel>
|
||||
</ItemsControl.Items>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Version Info -->
|
||||
<TextBlock Opacity="0.5" FontSize="12">
|
||||
<Run Text="Version " />
|
||||
<Run Text="{Binding Version}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</UserControl>
|
||||
11
src/ZonyLrcTools.Desktop/Views/Pages/HomePage.axaml.cs
Normal file
11
src/ZonyLrcTools.Desktop/Views/Pages/HomePage.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Views.Pages;
|
||||
|
||||
public partial class HomePage : UserControl
|
||||
{
|
||||
public HomePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
145
src/ZonyLrcTools.Desktop/Views/Pages/LyricsDownloadPage.axaml
Normal file
145
src/ZonyLrcTools.Desktop/Views/Pages/LyricsDownloadPage.axaml
Normal file
@@ -0,0 +1,145 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ZonyLrcTools.Desktop.ViewModels"
|
||||
x:Class="ZonyLrcTools.Desktop.Views.Pages.LyricsDownloadPage"
|
||||
x:DataType="vm:LyricsDownloadViewModel">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<!-- Title -->
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,24">
|
||||
<TextBlock Text="{Binding LyricsTitle}"
|
||||
FontSize="28"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding LyricsDescription}"
|
||||
Opacity="0.7"
|
||||
Margin="0,4,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Controls -->
|
||||
<Border Grid.Row="1"
|
||||
Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="16"
|
||||
Margin="0,0,0,16">
|
||||
<StackPanel Spacing="16">
|
||||
<!-- Row 1: Folder Selection -->
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding SelectedFolderPath}"
|
||||
Watermark="{Binding LyricsSelectFolder}"
|
||||
IsReadOnly="True"
|
||||
Margin="0,0,12,0" />
|
||||
<Button Grid.Column="1"
|
||||
Content="{Binding LyricsBrowse}"
|
||||
Command="{Binding SelectFolderCommand}"
|
||||
MinWidth="80" />
|
||||
</Grid>
|
||||
|
||||
<!-- Row 2: Options and Actions -->
|
||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto">
|
||||
<!-- Parallel Count -->
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{Binding LyricsParallel}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding ParallelCount}"
|
||||
Width="60"
|
||||
TextAlignment="Center"
|
||||
VerticalContentAlignment="Center" />
|
||||
|
||||
<!-- Spacer -->
|
||||
<Panel Grid.Column="2" />
|
||||
|
||||
<!-- Download Buttons -->
|
||||
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="8">
|
||||
<Button Command="{Binding StartDownloadCommand}"
|
||||
Content="{Binding LyricsStartDownload}"
|
||||
IsVisible="{Binding !IsDownloading}"
|
||||
Classes="accent"
|
||||
MinWidth="100" />
|
||||
<Button Command="{Binding CancelDownloadCommand}"
|
||||
Content="{Binding LyricsStopDownload}"
|
||||
IsVisible="{Binding IsDownloading}"
|
||||
MinWidth="100" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Progress Bar (visible during download) -->
|
||||
<StackPanel IsVisible="{Binding IsDownloading}" Spacing="8">
|
||||
<ProgressBar Value="{Binding ProgressPercentage}"
|
||||
Maximum="100"
|
||||
Height="6" />
|
||||
<TextBlock HorizontalAlignment="Center" FontSize="12" Opacity="0.8">
|
||||
<Run Text="{Binding CompletedCount}" />
|
||||
<Run Text=" / " />
|
||||
<Run Text="{Binding TotalCount}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- File List -->
|
||||
<Border Grid.Row="2"
|
||||
Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="1">
|
||||
<DataGrid ItemsSource="{Binding MusicFiles}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal"
|
||||
BorderThickness="0">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="{Binding ColumnSongName}"
|
||||
Binding="{Binding Name}"
|
||||
Width="*" />
|
||||
<DataGridTextColumn Header="{Binding ColumnArtist}"
|
||||
Binding="{Binding Artist}"
|
||||
Width="150" />
|
||||
<DataGridTextColumn Header="{Binding ColumnFilePath}"
|
||||
Binding="{Binding FilePath}"
|
||||
Width="250" />
|
||||
<DataGridTextColumn Header="{Binding ColumnStatus}"
|
||||
Binding="{Binding StatusMessage}"
|
||||
Width="100" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<Border Grid.Row="3"
|
||||
Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
Padding="16,12"
|
||||
Margin="0,16,0,0"
|
||||
CornerRadius="8">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<!-- Total -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock Text="{Binding CommonTotal}" Opacity="0.8" />
|
||||
<TextBlock Text="{Binding TotalCount}" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding CommonFiles}" Opacity="0.8" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Spacer -->
|
||||
<Panel Grid.Column="1" />
|
||||
|
||||
<!-- Success -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4" Margin="0,0,24,0">
|
||||
<TextBlock Text="{Binding CommonSuccess}" Opacity="0.8" />
|
||||
<TextBlock Text="{Binding CompletedCount}"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#4CAF50" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Failed -->
|
||||
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock Text="{Binding CommonFailed}" Opacity="0.8" />
|
||||
<TextBlock Text="{Binding FailedCount}"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#F44336" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Views.Pages;
|
||||
|
||||
public partial class LyricsDownloadPage : UserControl
|
||||
{
|
||||
public LyricsDownloadPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
141
src/ZonyLrcTools.Desktop/Views/Pages/SettingsPage.axaml
Normal file
141
src/ZonyLrcTools.Desktop/Views/Pages/SettingsPage.axaml
Normal file
@@ -0,0 +1,141 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ZonyLrcTools.Desktop.ViewModels"
|
||||
x:Class="ZonyLrcTools.Desktop.Views.Pages.SettingsPage"
|
||||
x:DataType="vm:SettingsViewModel">
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel Spacing="24">
|
||||
<!-- Title -->
|
||||
<StackPanel Margin="0,0,0,8">
|
||||
<TextBlock Text="{Binding SettingsTitle}"
|
||||
FontSize="28"
|
||||
FontWeight="SemiBold" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Language Settings -->
|
||||
<Border Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="24">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="{Binding SettingsLanguage}"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<CheckBox Content="{Binding SettingsFollowSystem}"
|
||||
IsChecked="{Binding FollowSystemLanguage}" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="16" IsEnabled="{Binding !FollowSystemLanguage}">
|
||||
<RadioButton GroupName="Language"
|
||||
Content="{Binding SettingsChinese}"
|
||||
IsChecked="{Binding IsChineseSelected}" />
|
||||
<RadioButton GroupName="Language"
|
||||
Content="{Binding SettingsEnglish}"
|
||||
IsChecked="{Binding IsEnglishSelected}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Network Settings -->
|
||||
<Border Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="24">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="{Binding SettingsProxy}"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<CheckBox Content="{Binding SettingsProxyEnable}"
|
||||
IsChecked="{Binding IsProxyEnabled}" />
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto,120" IsEnabled="{Binding IsProxyEnabled}">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding ProxyIp}"
|
||||
Watermark="{Binding SettingsProxyAddress}"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text=":"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0" />
|
||||
<NumericUpDown Grid.Column="2"
|
||||
Value="{Binding ProxyPort}"
|
||||
Minimum="1"
|
||||
Maximum="65535" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Lyrics Settings -->
|
||||
<Border Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="24">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="Lyrics"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<CheckBox Content="Enable translation lyrics"
|
||||
IsChecked="{Binding IsTranslationEnabled}" />
|
||||
|
||||
<CheckBox Content="One-line mode (original + translation on same line)"
|
||||
IsChecked="{Binding IsOneLineMode}" />
|
||||
|
||||
<CheckBox Content="Skip existing lyrics files"
|
||||
IsChecked="{Binding SkipExistingLyrics}" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
<TextBlock Text="File Encoding:"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox ItemsSource="{Binding AvailableEncodings}"
|
||||
SelectedItem="{Binding SelectedEncoding}"
|
||||
Width="150" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Lyrics Providers -->
|
||||
<Border Background="{DynamicResource NavigationBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="24">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="{Binding SettingsLyricsProvider}"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<ItemsControl ItemsSource="{Binding LyricsProviders}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:LyricsProviderSettingViewModel">
|
||||
<Border Background="{DynamicResource AppBackgroundBrush}"
|
||||
CornerRadius="4"
|
||||
Padding="12,8"
|
||||
Margin="0,4">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<CheckBox Grid.Column="0"
|
||||
IsChecked="{Binding IsEnabled}"
|
||||
Margin="0,0,12,0" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding Name}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0.5">
|
||||
<Run Text="Priority: " />
|
||||
<Run Text="{Binding Priority}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Actions -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="16" HorizontalAlignment="Right">
|
||||
<Button Content="{Binding SettingsReset}"
|
||||
Command="{Binding ResetToDefaultsCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</UserControl>
|
||||
11
src/ZonyLrcTools.Desktop/Views/Pages/SettingsPage.axaml.cs
Normal file
11
src/ZonyLrcTools.Desktop/Views/Pages/SettingsPage.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace ZonyLrcTools.Desktop.Views.Pages;
|
||||
|
||||
public partial class SettingsPage : UserControl
|
||||
{
|
||||
public SettingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
48
src/ZonyLrcTools.Desktop/ZonyLrcTools.Desktop.csproj
Normal file
48
src/ZonyLrcTools.Desktop/ZonyLrcTools.Desktop.csproj
Normal file
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Avalonia -->
|
||||
<PackageReference Include="Avalonia" />
|
||||
<PackageReference Include="Avalonia.Desktop" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<!-- MVVM -->
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<!-- DI & Hosting -->
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<!-- Logging -->
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<!-- Encoding -->
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||
<!-- Localization -->
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ZonyLrcTools.Common\ZonyLrcTools.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\ZonyLrcTools.Cli\config.yaml" Link="config.yaml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\ZonyLrcTools.Cli\BlockWords.json" Link="BlockWords.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
18
src/ZonyLrcTools.Desktop/app.manifest
Normal file
18
src/ZonyLrcTools.Desktop/app.manifest
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="ZonyLrcTools.Desktop"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 and Windows 11 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
Reference in New Issue
Block a user