refactor string cultureinfo

This commit is contained in:
Lightczx
2023-08-11 16:12:11 +08:00
parent a23043fb6d
commit 71fcbc367c
40 changed files with 328 additions and 173 deletions

View File

@@ -57,6 +57,7 @@ dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
dotnet_diagnostic.CA1000.severity = suggestion
[*.cs]
#### 命名样式 ####
@@ -162,7 +163,7 @@ dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
# VSTHRD111: Use ConfigureAwait(bool)
dotnet_diagnostic.VSTHRD111.severity = suggestion
dotnet_diagnostic.VSTHRD111.severity = silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
@@ -172,6 +173,156 @@ dotnet_diagnostic.SA1600.severity = none
dotnet_diagnostic.SA1601.severity = silent
dotnet_diagnostic.SA1602.severity = silent
# CA1008: 枚举应具有零值
dotnet_diagnostic.CA1008.severity = suggestion
# CA1010: 还应实现泛型接口
dotnet_diagnostic.CA1010.severity = suggestion
# CA1012: 抽象类型不应具有公共构造函数
dotnet_diagnostic.CA1012.severity = suggestion
# CA1024: 在适用处使用属性
dotnet_diagnostic.CA1024.severity = suggestion
# CA1034: 嵌套类型应不可见
dotnet_diagnostic.CA1034.severity = suggestion
# CA1036: 重写可比较类型中的方法
dotnet_diagnostic.CA1036.severity = suggestion
# CA1040: 避免使用空接口
dotnet_diagnostic.CA1040.severity = suggestion
# CA1044: 属性不应是只写的
dotnet_diagnostic.CA1044.severity = suggestion
# CA1043: 将整型或字符串参数用于索引器
dotnet_diagnostic.CA1043.severity = suggestion
# CA1046: 不要对引用类型重载相等运算符
dotnet_diagnostic.CA1046.severity = suggestion
# CA1051: 不要声明可见实例字段
dotnet_diagnostic.CA1051.severity = suggestion
# CA1052: 静态容器类型应为 Static 或 NotInheritable
dotnet_diagnostic.CA1052.severity = suggestion
# CA1058: 类型不应扩展某些基类型
dotnet_diagnostic.CA1058.severity = suggestion
# CA1063: 正确实现 IDisposable
dotnet_diagnostic.CA1063.severity = suggestion
# CA1065: 不要在意外的位置引发异常
dotnet_diagnostic.CA1065.severity = suggestion
# CA1066: 重写 Object.Equals 时实现 IEquatable
dotnet_diagnostic.CA1066.severity = suggestion
# CA1304: 指定 CultureInfo
dotnet_diagnostic.CA1304.severity = suggestion
# CA1305: 指定 IFormatProvider
dotnet_diagnostic.CA1305.severity = suggestion
# CA1307: 为了清晰起见,请指定 StringComparison
dotnet_diagnostic.CA1307.severity = suggestion
# CA1310: 为了确保正确,请指定 StringComparison
dotnet_diagnostic.CA1310.severity = suggestion
# CA1308: 将字符串规范化为大写
dotnet_diagnostic.CA1308.severity = suggestion
# CA1501: 避免过度继承
dotnet_diagnostic.CA1501.severity = suggestion
# CA1502: 避免过度复杂性
dotnet_diagnostic.CA1502.severity = suggestion
# CA1505: 避免使用无法维护的代码
dotnet_diagnostic.CA1505.severity = suggestion
# CA1506: 避免过度的类耦合
dotnet_diagnostic.CA1506.severity = suggestion
# CA1508: 避免死条件代码
dotnet_diagnostic.CA1508.severity = suggestion
# CA1810: 以内联方式初始化引用类型的静态字段
dotnet_diagnostic.CA1810.severity = suggestion
# CA1813: 避免使用非密封特性
dotnet_diagnostic.CA1813.severity = suggestion
# CA1814: 与多维数组相比,首选使用交错数组
dotnet_diagnostic.CA1814.severity = suggestion
# CA1819: 属性不应返回数组
dotnet_diagnostic.CA1819.severity = suggestion
# CA1820: 使用字符串长度测试是否有空字符串
dotnet_diagnostic.CA1820.severity = suggestion
# CA1823: 避免未使用的私有字段
dotnet_diagnostic.CA1823.severity = suggestion
# CA1849: 当在异步方法中时,调用异步方法
dotnet_diagnostic.CA1849.severity = suggestion
# CA1852: 密封内部类型
dotnet_diagnostic.CA1852.severity = suggestion
# CA2000: 丢失范围之前释放对象
dotnet_diagnostic.CA2000.severity = suggestion
# CA2002: 不要锁定具有弱标识的对象
dotnet_diagnostic.CA2002.severity = suggestion
# CA2007: 考虑对等待的任务调用 ConfigureAwait
dotnet_diagnostic.CA2007.severity = suggestion
# CA2008: 不要在未传递 TaskScheduler 的情况下创建任务
dotnet_diagnostic.CA2008.severity = suggestion
# CA2100: 检查 SQL 查询是否存在安全漏洞
dotnet_diagnostic.CA2100.severity = suggestion
# CA2109: 检查可见的事件处理程序
dotnet_diagnostic.CA2109.severity = suggestion
# CA2119: 密封满足私有接口的方法
dotnet_diagnostic.CA2119.severity = suggestion
# CA2153: 不要捕获损坏状态异常
dotnet_diagnostic.CA2153.severity = suggestion
# CA2201: 不要引发保留的异常类型
dotnet_diagnostic.CA2201.severity = suggestion
# CA2207: 以内联方式初始化值类型的静态字段
dotnet_diagnostic.CA2207.severity = suggestion
# CA2213: 应释放可释放的字段
dotnet_diagnostic.CA2213.severity = suggestion
# CA2214: 不要在构造函数中调用可重写的方法
dotnet_diagnostic.CA2214.severity = suggestion
# CA2215: Dispose 方法应调用基类释放
dotnet_diagnostic.CA2215.severity = suggestion
# CA2216: 可释放类型应声明终结器
dotnet_diagnostic.CA2216.severity = suggestion
# CA2227: 集合属性应为只读
dotnet_diagnostic.CA2227.severity = suggestion
# CA2251: 使用 “string.Equals”
dotnet_diagnostic.CA2251.severity = suggestion
[*.vb]
#### 命名样式 ####

View File

@@ -78,6 +78,9 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
context.RegisterSyntaxNodeAction(HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching, expressions);
context.RegisterSyntaxNodeAction(HandleIsPatternShouldUseRecursivePattern, SyntaxKind.IsPatternExpression);
context.RegisterSyntaxNodeAction(HandleArgumentNullExceptionThrowIfNull, SyntaxKind.SuppressNullableWarningExpression);
// TODO add analyzer for unnecessary IServiceProvider registration
// TODO add analyzer for Singlton service use Scoped or Transient services
}
private static void HandleTypeShouldBeInternal(SyntaxNodeAnalysisContext context)

View File

@@ -53,13 +53,13 @@ public class JsonSerializeTest
Assert.AreEqual(sample[111], "12");
}
private class Sample
private sealed class Sample
{
public int A { get => B; set => B = value; }
public int B { get; set; }
}
private class StringNumberSample
private sealed class StringNumberSample
{
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public int A { get; set; }

View File

@@ -0,0 +1 @@
CA1501: 6

View File

@@ -10,6 +10,7 @@ using Snap.Hutao.Control.Animation;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Service.Notification;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;
@@ -110,7 +111,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
if (exception is HttpRequestException httpRequestException)
{
infoBarService.Error(httpRequestException, string.Format(SH.ControlImageCompositionImageHttpRequest, uri));
infoBarService.Error(httpRequestException, SH.ControlImageCompositionImageHttpRequest.Format(uri));
}
else
{

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Markup;
using System.Globalization;
namespace Snap.Hutao.Control.Markup;
@@ -20,6 +21,6 @@ internal sealed class ResourceStringExtension : MarkupExtension
/// <inheritdoc/>
protected override object ProvideValue()
{
return SH.ResourceManager.GetString(Name ?? string.Empty) ?? Name ?? string.Empty;
return SH.ResourceManager.GetString(Name ?? string.Empty, CultureInfo.CurrentCulture) ?? Name ?? string.Empty;
}
}

View File

@@ -7,6 +7,6 @@ namespace Snap.Hutao.Core.Annotation;
/// 高质量代码
/// </summary>
[AttributeUsage(AttributeTargets.All, Inherited = false)]
internal class HighQualityAttribute : Attribute
internal sealed class HighQualityAttribute : Attribute
{
}

View File

@@ -15,7 +15,7 @@ internal sealed class DatabaseCorruptedException : Exception
/// <param name="message">消息</param>
/// <param name="innerException">内部错误</param>
public DatabaseCorruptedException(string message, Exception? innerException)
: base(string.Format(SH.CoreExceptionServiceDatabaseCorruptedMessage, $"{message}\n{innerException?.Message}"), innerException)
: base(SH.CoreExceptionServiceDatabaseCorruptedMessage.Format($"{message}\n{innerException?.Message}"), innerException)
{
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using System.Collections;
using System.Globalization;
using System.Text;
namespace Snap.Hutao.Core.ExceptionService;
@@ -25,7 +26,7 @@ internal sealed class ExceptionFormat
foreach (DictionaryEntry entry in exception.Data)
{
builder.AppendLine($"{entry.Key}:[{TypeNameHelper.GetTypeDisplayName(entry.Value)}]:entry.Value");
builder.AppendLine(CultureInfo.CurrentCulture, $"[{TypeNameHelper.GetTypeDisplayName(entry.Value)}]:{entry.Key}:{entry.Value}");
}
builder.AppendLine(SectionSeparator);

View File

@@ -15,7 +15,7 @@ internal sealed class UserdataCorruptedException : Exception
/// <param name="message">消息</param>
/// <param name="innerException">内部错误</param>
public UserdataCorruptedException(string message, Exception? innerException)
: base(string.Format(SH.CoreExceptionServiceUserdataCorruptedMessage, $"{message}\n{innerException?.Message}"), innerException)
: base(SH.CoreExceptionServiceUserdataCorruptedMessage.Format($"{message}\n{innerException?.Message}"), innerException)
{
}
}

View File

@@ -19,7 +19,7 @@ internal static class MD5
/// <returns>文件 Md5 摘要</returns>
public static async ValueTask<string> HashFileAsync(string filePath, CancellationToken token = default)
{
await using (FileStream stream = File.OpenRead(filePath))
using (FileStream stream = File.OpenRead(filePath))
{
return await HashAsync(stream, token).ConfigureAwait(false);
}
@@ -33,10 +33,7 @@ internal static class MD5
/// <returns>流 Md5 摘要</returns>
public static async ValueTask<string> HashAsync(Stream stream, CancellationToken token = default)
{
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
byte[] bytes = await md5.ComputeHashAsync(stream, token).ConfigureAwait(false);
return System.Convert.ToHexString(bytes);
}
byte[] bytes = await System.Security.Cryptography.MD5.HashDataAsync(stream, token).ConfigureAwait(false);
return System.Convert.ToHexString(bytes);
}
}

View File

@@ -33,7 +33,7 @@ internal static class XXH64
/// <returns>摘要</returns>
public static async ValueTask<string> HashFileAsync(string path, CancellationToken token = default)
{
await using (FileStream stream = File.OpenRead(path))
using (FileStream stream = File.OpenRead(path))
{
return await HashAsync(stream, token).ConfigureAwait(false);
}

View File

@@ -22,25 +22,26 @@ internal static class IniSerializer
{
while (reader.ReadLine() is { } line)
{
if (line.Length <= 0)
if (string.IsNullOrEmpty(line))
{
continue;
}
if (line[0] == '[')
ReadOnlySpan<char> lineSpan = line;
if (lineSpan[0] is '[')
{
yield return new IniSection(line[1..^1]);
yield return new IniSection(lineSpan[1..^1].ToString());
}
if (line[0] == ';')
if (lineSpan[0] is ';')
{
yield return new IniComment(line[1..]);
yield return new IniComment(lineSpan[1..].ToString());
}
if (line.IndexOf('=') > 0)
if (lineSpan.TrySplitIntoTwo('=', out ReadOnlySpan<char> left, out ReadOnlySpan<char> right))
{
string[] parameters = line.Split('=', 2, StringSplitOptions.TrimEntries);
yield return new IniParameter(parameters[0], parameters[1]);
yield return new IniParameter(left.Trim().ToString(), right.Trim().ToString());
}
}
}

View File

@@ -98,7 +98,7 @@ internal static class PickerExtension
.GetRequiredService<IInfoBarService>()
.Warning(
SH.CoreIOPickerExtensionPickerExceptionInfoBarTitle,
string.Format(SH.CoreIOPickerExtensionPickerExceptionInfoBarMessage, exception.Message));
SH.CoreIOPickerExtensionPickerExceptionInfoBarMessage.Format(exception.Message));
}
}
}

View File

@@ -80,6 +80,7 @@ internal readonly struct ValueFile
}
/// <inheritdoc/>
[SuppressMessage("", "CA1307")]
public override int GetHashCode()
{
return value.GetHashCode();

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.Json.Annotation;
/// Json 枚举类型
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
internal class JsonEnumAttribute : Attribute
internal sealed class JsonEnumAttribute : Attribute
{
private static readonly Type UnsafeEnumConverterType = typeof(UnsafeEnumConverter<>);

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Globalization;
namespace Snap.Hutao.Core.Json.Converter;
/// <summary>
@@ -9,12 +11,14 @@ namespace Snap.Hutao.Core.Json.Converter;
[HighQuality]
internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
private const string Format = "yyyy-MM-dd HH:mm:ss";
/// <inheritdoc/>
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.GetString() is { } dataTimeString)
{
return DateTimeOffset.Parse(dataTimeString);
return DateTimeOffset.ParseExact(dataTimeString, Format, CultureInfo.CurrentCulture);
}
return default;
@@ -23,6 +27,6 @@ internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
writer.WriteStringValue(value.ToString(Format, CultureInfo.CurrentCulture));
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.Extensions.Primitives;
using System.Globalization;
namespace Snap.Hutao.Core.Json.Converter;
@@ -35,7 +36,7 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEn
// TODO: Use Collection Literals
foreach (StringSegment id in new StringTokenizer(source, new[] { Comma }))
{
yield return int.Parse(id.AsSpan());
yield return int.Parse(id.AsSpan(), CultureInfo.CurrentCulture);
}
}
}

View File

@@ -42,10 +42,10 @@ internal sealed class Activation : IActivation
/// </summary>
public const string ImportUIAFFromClipboard = nameof(ImportUIAFFromClipboard);
private const string CategoryAchievement = "achievement";
private const string CategoryDailyNote = "dailynote";
private const string UrlActionImport = "/import";
private const string UrlActionRefresh = "/refresh";
private const string CategoryAchievement = "ACHIEVEMENT";
private const string CategoryDailyNote = "DAILYNOTE";
private const string UrlActionImport = "/IMPORT";
private const string UrlActionRefresh = "/REFRESH";
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
@@ -187,9 +187,9 @@ internal sealed class Activation : IActivation
{
UriBuilder builder = new(uri);
string category = builder.Host.ToLowerInvariant();
string action = builder.Path.ToLowerInvariant();
string parameter = builder.Query.ToLowerInvariant();
string category = builder.Host.ToUpperInvariant();
string action = builder.Path.ToUpperInvariant();
string parameter = builder.Query.ToUpperInvariant();
switch (category)
{

View File

@@ -11,12 +11,7 @@ namespace Snap.Hutao.Core.Setting;
[HighQuality]
internal static class LocalSetting
{
private static readonly ApplicationDataContainer Container;
static LocalSetting()
{
Container = ApplicationData.Current.LocalSettings;
}
private static readonly ApplicationDataContainer Container = ApplicationData.Current.LocalSettings;
/// <inheritdoc cref="Get{T}(string, T)"/>
public static byte Get(string key, byte defaultValue)

View File

@@ -18,13 +18,15 @@ internal static class DispatcherQueueExtension
/// <param name="action">执行的回调</param>
public static void Invoke(this DispatcherQueue dispatcherQueue, Action action)
{
ManualResetEventSlim blockEvent = new();
dispatcherQueue.TryEnqueue(() =>
using (ManualResetEventSlim blockEvent = new())
{
action();
blockEvent.Set();
});
dispatcherQueue.TryEnqueue(() =>
{
action();
blockEvent.Set();
});
blockEvent.Wait();
blockEvent.Wait();
}
}
}

View File

@@ -161,7 +161,7 @@ internal static class TypeNameHelper
}
}
int genericPartIndex = type.Name.IndexOf('`');
int genericPartIndex = type.Name.AsSpan().IndexOf('`');
if (genericPartIndex <= 0)
{
builder.Append(type.Name);

View File

@@ -29,7 +29,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMes
{
private readonly TWindow window;
private readonly IServiceProvider serviceProvider;
private readonly WindowSubclass<TWindow>? subclass;
private readonly WindowSubclass<TWindow> subclass;
private ExtendedWindow(TWindow window, IServiceProvider serviceProvider)
{
@@ -63,7 +63,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMes
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
WindowOptions options = window.WindowOptions;
window.AppWindow.Title = string.Format(SH.AppNameAndVersion, hutaoOptions.Version);
window.AppWindow.Title = SH.AppNameAndVersion.Format(hutaoOptions.Version);
window.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico"));
ExtendsContentIntoTitleBar();
@@ -79,7 +79,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMes
UpdateSystemBackdrop(appOptions.BackdropType);
appOptions.PropertyChanged += OnOptionsPropertyChanged;
subclass!.Initialize();
subclass.Initialize();
serviceProvider.GetRequiredService<IMessenger>().Register(this);
@@ -91,7 +91,8 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMes
{
if (e.PropertyName == nameof(AppOptions.BackdropType))
{
UpdateSystemBackdrop(((AppOptions)sender!).BackdropType);
ArgumentNullException.ThrowIfNull(sender);
UpdateSystemBackdrop(((AppOptions)sender).BackdropType);
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Globalization;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Extension;
@@ -27,4 +28,16 @@ internal static class StringExtension
{
return source.AsSpan().TrimEnd(value).ToString();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Format(this string value, object? arg)
{
return string.Format(CultureInfo.CurrentCulture, value, arg);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Format(this string value, object? arg0, object? arg1)
{
return string.Format(CultureInfo.CurrentCulture, value, arg0, arg1);
}
}

View File

@@ -13,9 +13,7 @@ namespace Snap.Hutao.Model.Entity.Configuration;
[HighQuality]
internal sealed class JsonTextValueConverter<TProperty> : ValueConverter<TProperty, string>
{
/// <summary>
/// Initializes a new instance of the <see cref="JsonTextValueConverter{TProperty}"/> class.
/// </summary>
[SuppressMessage("", "SH007")]
public JsonTextValueConverter()
: base(
obj => JsonSerializer.Serialize(obj, JsonOptions.Default),

View File

@@ -14,6 +14,7 @@ namespace Snap.Hutao.Model.Entity.Configuration;
internal sealed class UserConfiguration : IEntityTypeConfiguration<User>
{
/// <inheritdoc/>
[SuppressMessage("", "SH007")]
public void Configure(EntityTypeBuilder<User> builder)
{
builder.Property(e => e.CookieToken)

View File

@@ -7,6 +7,7 @@ using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization;
namespace Snap.Hutao.Model.Entity;
@@ -119,7 +120,7 @@ internal sealed partial class GachaItem
ArchiveId = archiveId,
GachaType = item.GachaType,
QueryType = item.UIGFGachaType,
ItemId = uint.Parse(item.ItemId),
ItemId = uint.Parse(item.ItemId, CultureInfo.CurrentCulture),
Time = item.Time,
Id = item.Id,
};

View File

@@ -32,7 +32,7 @@ internal sealed class SpiralAbyssEntry : ObservableObject,
/// 视图 中使用的计划 Id 字符串
/// </summary>
[NotMapped]
public string Schedule { get => string.Format(SH.ModelEntitySpiralAbyssScheduleFormat, ScheduleId); }
public string Schedule { get => SH.ModelEntitySpiralAbyssScheduleFormat.Format(ScheduleId); }
/// <summary>
/// Uid

View File

@@ -47,7 +47,7 @@ internal sealed class FetterInfo
/// </summary>
public string BirthFormatted
{
get => string.Format(SH.ModelMetadataFetterInfoBirthdayFormat, BirthMonth, BirthDay);
get => SH.ModelMetadataFetterInfoBirthdayFormat.Format(BirthMonth, BirthDay);
}
/// <summary>

View File

@@ -20,7 +20,7 @@ internal sealed class AvatarNameCardPicConverter : ValueConverter<Avatar.Avatar?
{
if (avatar is null)
{
return null!;
return default!;
}
string avatarName = ReplaceSpecialCaseNaming(avatar.Icon["UI_AvatarIcon_".Length..]);

View File

@@ -5,6 +5,7 @@ using Snap.Hutao.Control;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Primitive;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
@@ -65,8 +66,8 @@ internal sealed partial class DescriptionsParametersDescriptor : ValueConverter<
{
if (match.Success)
{
int index = int.Parse(match.Groups[1].Value) - 1;
return ParameterFormat.Format($"{{0:{match.Groups[2].Value}}}", paramList[index]);
int index = int.Parse(match.Groups[1].Value, CultureInfo.CurrentCulture) - 1;
return ParameterFormat.Format($"{{0:{match.Groups[2].Value}}}", paramList[index], CultureInfo.CurrentCulture);
}
else
{

View File

@@ -114,7 +114,7 @@ internal static class FightPropertyFormat
{
FormatMethod.Integer => $"{MathF.Round(value, MidpointRounding.AwayFromZero)}",
FormatMethod.Percent => $"{value:P1}",
_ => value.ToString(),
_ => $"{value}",
};
}
}

View File

@@ -17,7 +17,7 @@ internal sealed class ParameterFormat : IFormatProvider, ICustomFormatter
/// <param name="str">字符串</param>
/// <param name="param">参数</param>
/// <returns>格式化的字符串</returns>
public static string Format(string str, object param)
public static string Format(string str, object param, IFormatProvider? formatProvider = default)
{
return string.Format(LazyFormat.Value, str, param);
}
@@ -29,16 +29,16 @@ internal sealed class ParameterFormat : IFormatProvider, ICustomFormatter
switch (fmtSpan.Length)
{
case 3: // FnP
return string.Format($"{{0:P{fmtSpan[1]}}}", arg);
return string.Format(formatProvider, $"{{0:P{fmtSpan[1]}}}", arg);
case 2: // Fn
return string.Format($"{{0:{fmtSpan}}}", arg);
return string.Format(formatProvider, $"{{0:{fmtSpan}}}", arg);
case 1: // P I
switch (fmtSpan[0])
{
case 'P':
return string.Format($"{{0:P0}}", arg);
return string.Format(formatProvider, $"{{0:P0}}", arg);
case 'I':
return arg is null ? "0" : ((IConvertible)arg).ToInt32(default).ToString();
return arg is null ? "0" : ((IConvertible)arg).ToInt32(default).ToString(formatProvider);
}
break;

View File

@@ -2733,6 +2733,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 请输入正确的邮箱 的本地化字符串。
/// </summary>
internal static string ViewModelHutaoPassportEmailNotValidHint {
get {
return ResourceManager.GetString("ViewModelHutaoPassportEmailNotValidHint", resourceCulture);
}
}
/// <summary>
/// 查找类似 剪贴板中的文本格式不正确 的本地化字符串。
/// </summary>

View File

@@ -824,12 +824,12 @@
<data name="ViewDialogDailyNoteNotificationExpeditionNotify" xml:space="preserve">
<value>探索派遣完成提醒</value>
</data>
<data name="ViewDialogDailyNoteNotificationResinNotifyThreshold" xml:space="preserve">
<value>原粹树脂提醒阈值</value>
</data>
<data name="ViewDialogDailyNoteNotificationHomeCoinNotifyThreshold" xml:space="preserve">
<value>洞天宝钱提醒阈值</value>
</data>
<data name="ViewDialogDailyNoteNotificationResinNotifyThreshold" xml:space="preserve">
<value>原粹树脂提醒阈值</value>
</data>
<data name="ViewDialogDailyNoteNotificationShowInHomeWidget" xml:space="preserve">
<value>在主页显示卡片</value>
</data>
@@ -1064,6 +1064,9 @@
<data name="ViewModelGachaLogUploadToHutaoCloudProgress" xml:space="preserve">
<value>正在上传到胡桃云服务</value>
</data>
<data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve">
<value>请输入正确的邮箱</value>
</data>
<data name="ViewModelImportFromClipboardErrorTitle" xml:space="preserve">
<value>剪贴板中的文本格式不正确</value>
</data>

View File

@@ -71,6 +71,7 @@
<None Remove="Assets\Wide310x150Logo.scale-100.png" />
<None Remove="Assets\Wide310x150Logo.scale-200.png" />
<None Remove="Assets\Wide310x150Logo.scale-400.png" />
<None Remove="CodeMetricsConfig.txt" />
<None Remove="Control\Panel\PanelSelector.xaml" />
<None Remove="Control\Theme\FontStyle.xaml" />
<None Remove="GuideWindow.xaml" />
@@ -161,6 +162,7 @@
<!-- Analyzer Files -->
<ItemGroup>
<AdditionalFiles Include="CodeMetricsConfig.txt" />
<AdditionalFiles Include="IdentityStructs.json" />
<AdditionalFiles Include="NativeMethods.json" />
<AdditionalFiles Include="NativeMethods.txt" />

View File

@@ -150,7 +150,7 @@
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingStorageOpenAction}"
Command="{Binding Experimental.OpenDataFolderCommand}"
Command="{Binding OpenDataFolderCommand}"
Description="{shcm:ResourceString Name=ViewPageSettingDataFolderDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingDataFolderHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xEC25;}"
@@ -168,7 +168,7 @@
Margin="0,4,0,0"
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingStorageOpenAction}"
Command="{Binding Experimental.OpenCacheFolderCommand}"
Command="{Binding OpenCacheFolderCommand}"
Description="{shcm:ResourceString Name=ViewPageSettingCacheFolderDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingCacheFolderHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE8B7;}"
@@ -227,7 +227,7 @@
Severity="Error"/>
<cwc:SettingsCard
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingDangerousAction}"
Command="{Binding Experimental.DeleteUsersCommand}"
Command="{Binding DeleteUsersCommand}"
Description="{shcm:ResourceString Name=ViewPageSettingDeleteUserDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingDeleteUserHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE756;}"

View File

@@ -1,58 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.EntityFrameworkCore;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Entity.Database;
using Windows.System;
namespace Snap.Hutao.ViewModel;
/// <summary>
/// 实验性功能视图模型
/// </summary>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Scoped)]
internal sealed partial class ExperimentalFeaturesViewModel : ObservableObject
{
private readonly IServiceProvider serviceProvider;
[Command("OpenCacheFolderCommand")]
private Task OpenCacheFolderAsync()
{
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
return Launcher.LaunchFolderPathAsync(hutaoOptions.LocalCache).AsTask();
}
[Command("OpenDataFolderCommand")]
private Task OpenDataFolderAsync()
{
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
return Launcher.LaunchFolderPathAsync(hutaoOptions.DataFolder).AsTask();
}
[Command("DeleteUsersCommand")]
private async Task DangerousDeleteUsersAsync()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
ContentDialogResult result = await scope.ServiceProvider
.GetRequiredService<IContentDialogFactory>()
.CreateForConfirmCancelAsync(SH.ViewDialogSettingDeleteUserDataTitle, SH.ViewDialogSettingDeleteUserDataContent)
.ConfigureAwait(false);
if (result == ContentDialogResult.Primary)
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users.ExecuteDeleteAsync().ConfigureAwait(false);
AppInstance.Restart(string.Empty);
}
}
}
}

View File

@@ -1,12 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Common;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Web.Hutao;
using Snap.Hutao.Web.Response;
using System.Text.RegularExpressions;
namespace Snap.Hutao.ViewModel;
@@ -17,9 +19,12 @@ namespace Snap.Hutao.ViewModel;
[Injection(InjectAs.Scoped)]
internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
{
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly HomaPassportClient homaPassportClient;
private readonly INavigationService navigationService;
private readonly HutaoUserOptions hutaoUserOptions;
private readonly IServiceProvider serviceProvider;
private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext;
private string? userName;
private string? password;
@@ -40,22 +45,16 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
/// </summary>
public string? VerifyCode { get => verifyCode; set => SetProperty(ref verifyCode, value); }
/// <inheritdoc/>
protected override Task OpenUIAsync()
{
return Task.CompletedTask;
}
[Command("RegisterVerifyCommand")]
private Task RegisterVerifyAsync()
{
return VerifyAsync(false);
return VerifyAsync(false).AsTask();
}
[Command("RegisterCommand")]
private async Task RegisterAsync()
{
if (UserName == null || Password == null || VerifyCode == null)
if (UserName is null || Password is null || VerifyCode is null)
{
return;
}
@@ -65,13 +64,12 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
if (response.IsOk())
{
SaveUserNameAndPassword();
serviceProvider.GetRequiredService<IInfoBarService>().Information(response.Message);
infoBarService.Information(response.Message);
await taskContext.SwitchToMainThreadAsync();
serviceProvider.GetRequiredService<HutaoUserOptions>().LoginSucceed(UserName, response.Data);
hutaoUserOptions.LoginSucceed(UserName, response.Data);
await serviceProvider
.GetRequiredService<INavigationService>()
await navigationService
.NavigateAsync<View.Page.SettingPage>(INavigationAwaiter.Default, true)
.ConfigureAwait(false);
}
@@ -80,13 +78,13 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
[Command("ResetPasswordVerifyCommand")]
private Task ResetPasswordVerifyAsync()
{
return VerifyAsync(true);
return VerifyAsync(true).AsTask();
}
[Command("ResetPasswordCommand")]
private async Task ResetPasswordAsync()
{
if (UserName == null || Password == null || VerifyCode == null)
if (UserName is null || Password is null || VerifyCode is null)
{
return;
}
@@ -96,13 +94,12 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
if (response.IsOk())
{
SaveUserNameAndPassword();
serviceProvider.GetRequiredService<IInfoBarService>().Information(response.Message);
infoBarService.Information(response.Message);
await taskContext.SwitchToMainThreadAsync();
serviceProvider.GetRequiredService<HutaoUserOptions>().LoginSucceed(UserName, response.Data);
hutaoUserOptions.LoginSucceed(UserName, response.Data);
await serviceProvider
.GetRequiredService<INavigationService>()
await navigationService
.NavigateAsync<View.Page.SettingPage>(INavigationAwaiter.Default, true)
.ConfigureAwait(false);
}
@@ -111,7 +108,7 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
[Command("LoginCommand")]
private async Task LoginAsync()
{
if (UserName == null || Password == null)
if (UserName is null || Password is null)
{
return;
}
@@ -121,32 +118,36 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
if (response.IsOk())
{
SaveUserNameAndPassword();
serviceProvider.GetRequiredService<IInfoBarService>().Information(response.Message);
infoBarService.Information(response.Message);
await taskContext.SwitchToMainThreadAsync();
serviceProvider.GetRequiredService<HutaoUserOptions>().LoginSucceed(UserName, response.Data);
hutaoUserOptions.LoginSucceed(UserName, response.Data);
await serviceProvider
.GetRequiredService<INavigationService>()
await navigationService
.NavigateAsync<View.Page.SettingPage>(INavigationAwaiter.Default, true)
.ConfigureAwait(false);
}
}
private async Task VerifyAsync(bool isResetPassword)
private async ValueTask VerifyAsync(bool isResetPassword)
{
if (UserName == null)
if (string.IsNullOrEmpty(UserName))
{
return;
}
if (!UserName.IsEmail())
{
infoBarService.Warning(SH.ViewModelHutaoPassportEmailNotValidHint);
}
Response response = await homaPassportClient.VerifyAsync(UserName, isResetPassword).ConfigureAwait(false);
serviceProvider.GetRequiredService<IInfoBarService>().Information(response.Message);
}
private void SaveUserNameAndPassword()
{
if (UserName != null && Password != null)
if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password))
{
LocalSetting.Set(SettingKeys.PassportUserName, UserName);
LocalSetting.Set(SettingKeys.PassportPassword, Password);

View File

@@ -1,11 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Factory.Abstraction;
@@ -13,7 +14,6 @@ using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Navigation;
@@ -22,6 +22,7 @@ using Snap.Hutao.View.Dialog;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using Windows.System;
namespace Snap.Hutao.ViewModel;
@@ -35,13 +36,9 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
{
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly AppDbContext appDbContext;
private readonly IGameService gameService;
private readonly ILogger<SettingViewModel> logger;
private readonly AppOptions options;
private readonly RuntimeOptions hutaoOptions;
private readonly RuntimeOptions runtimeOptions;
private readonly HutaoUserOptions hutaoUserOptions;
private readonly ExperimentalFeaturesViewModel experimental;
private NameValue<BackdropType>? selectedBackdropType;
private NameValue<string>? selectedCulture;
@@ -54,7 +51,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
/// <summary>
/// 胡桃选项
/// </summary>
public RuntimeOptions HutaoOptions { get => hutaoOptions; }
public RuntimeOptions HutaoOptions { get => runtimeOptions; }
/// <summary>
/// 胡桃用户选项
@@ -95,11 +92,6 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
}
}
/// <summary>
/// 实验性功能
/// </summary>
public ExperimentalFeaturesViewModel Experimental { get => experimental; }
/// <inheritdoc/>
protected override Task OpenUIAsync()
{
@@ -216,4 +208,36 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
{
serviceProvider.GetRequiredService<INavigationService>().Navigate<View.Page.HutaoPassportPage>(INavigationAwaiter.Default);
}
[Command("OpenCacheFolderCommand")]
private Task OpenCacheFolderAsync()
{
return Launcher.LaunchFolderPathAsync(runtimeOptions.LocalCache).AsTask();
}
[Command("OpenDataFolderCommand")]
private Task OpenDataFolderAsync()
{
return Launcher.LaunchFolderPathAsync(runtimeOptions.DataFolder).AsTask();
}
[Command("DeleteUsersCommand")]
private async Task DangerousDeleteUsersAsync()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
ContentDialogResult result = await scope.ServiceProvider
.GetRequiredService<IContentDialogFactory>()
.CreateForConfirmCancelAsync(SH.ViewDialogSettingDeleteUserDataTitle, SH.ViewDialogSettingDeleteUserDataContent)
.ConfigureAwait(false);
if (result == ContentDialogResult.Primary)
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users.ExecuteDeleteAsync().ConfigureAwait(false);
AppInstance.Restart(string.Empty);
}
}
}
}