support language switch

This commit is contained in:
DismissedLight
2023-02-20 16:04:23 +08:00
parent ffce055d75
commit 400e097fa7
52 changed files with 581 additions and 222 deletions

View File

@@ -49,9 +49,11 @@
<!-- Brushes -->
<SolidColorBrush x:Key="AvatarPropertyAddValueBrush" Color="{ThemeResource AvatarPropertyAddValueColor}"/>
<!-- Uris -->
<x:String x:Key="DocumentLink_MhyAccountSwitch">https://hut.ao/features/mhy-account-switch.html#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96-cookie</x:String>
<x:String x:Key="DocumentLink_MhyAccountSwitch">https://hut.ao/features/mhy-account-switch.html</x:String>
<x:String x:Key="DocumentLink_BugReport">https://hut.ao/statements/bug-report.html</x:String>
<x:String x:Key="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
<x:String x:Key="DocumentLink_Translate">https://translate.hut.ao</x:String>
<x:String x:Key="Sponsor_Afadian">https://afdian.net/a/DismissedLight</x:String>
<x:String x:Key="UI_ItemIcon_None">https://static.snapgenshin.com/Bg/UI_ItemIcon_None.png</x:String>
<x:String x:Key="UI_ImgSign_ItemIcon">https://static.snapgenshin.com/Bg/UI_ImgSign_ItemIcon.png</x:String>

View File

@@ -12,7 +12,7 @@ namespace Snap.Hutao.Control;
/// when object is not used anymore.
/// </summary>
[HighQuality]
public class BindingProxy : DependencyObject
internal sealed class BindingProxy : DependencyObject
{
private static readonly DependencyProperty DataContextProperty = Property<BindingProxy>.Depend<object>(nameof(DataContext));

View File

@@ -20,7 +20,7 @@ namespace Snap.Hutao.Control.Image;
/// 为其他图像类控件提供基类
/// </summary>
[HighQuality]
public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
{
private static readonly DependencyProperty SourceProperty = Property<CompositionImage>.Depend(nameof(Source), default(Uri), OnSourceChanged);
private static readonly DependencyProperty EnableLazyLoadingProperty = Property<CompositionImage>.DependBoxed<bool>(nameof(EnableLazyLoading), BoxedValues.True);

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Control.Theme;
/// 主题帮助工具类
/// </summary>
[HighQuality]
public static class ThemeHelper
internal static class ThemeHelper
{
/// <summary>
/// 判断主题是否相等

View File

@@ -20,7 +20,7 @@ namespace Snap.Hutao.Core.Caching;
[Injection(InjectAs.Singleton, typeof(IImageCache))]
[HttpClient(HttpClientConfigration.Default)]
[PrimaryHttpMessageHandler(MaxConnectionsPerServer = 8)]
public sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
{
private const string CacheFolderName = nameof(ImageCache);

View File

@@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.Database;
/// 数据库集合扩展
/// </summary>
[HighQuality]
public static class DbSetExtension
internal static class DbSetExtension
{
/// <summary>
/// 获取对应的数据库上下文

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.Core.Database;
/// 可枚举扩展
/// </summary>
[HighQuality]
public static class EnumerableExtension
internal static class EnumerableExtension
{
/// <summary>
/// 获取选中的值或默认值

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.Database;
/// 必须实现该接口
/// </summary>
[HighQuality]
public interface ISelectable
internal interface ISelectable
{
/// <summary>
/// 获取或设置当前项的选中状态

View File

@@ -19,7 +19,6 @@ internal static class QueryableExtension
/// <typeparam name="TSource">源类型</typeparam>
/// <param name="source">源</param>
/// <param name="predicate">条件</param>
/// <param name="token">取消令牌</param>
/// <returns>SQL返回个数</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ExecuteDeleteWhere<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)

View File

@@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation;
/// <summary>
/// 注入方法
/// </summary>
public enum InjectAs
[HighQuality]
internal enum InjectAs
{
/// <summary>
/// 指示应注册为单例对象

View File

@@ -7,8 +7,9 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation;
/// 指示被标注的类型可注入
/// 由源生成器生成注入代码
/// </summary>
[HighQuality]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class InjectionAttribute : Attribute
internal sealed class InjectionAttribute : Attribute
{
/// <summary>
/// 指示该类将注入为不带有接口实现的类

View File

@@ -3,8 +3,12 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using System.Diagnostics;
using System.Globalization;
namespace Snap.Hutao.Core.DependencyInjection;
@@ -44,6 +48,9 @@ internal static class IocConfiguration
#endif
context.Database.Migrate();
}
SettingEntry entry = context.Settings.SingleOrAdd(SettingEntry.Culture, CultureInfo.CurrentCulture.Name);
Localization.Initialize(entry.Value!);
}
return services.AddDbContext<AppDbContext>(builder =>

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.ExpressionService;
/// </summary>
/// <typeparam name="TTo">Target type</typeparam>
[HighQuality]
public static class CastTo<TTo>
internal static class CastTo<TTo>
{
/// <summary>
/// Casts <see cref="s"/> to <see cref="TTo"/>.

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.IO.Bits;
/// </summary>
[HighQuality]
[DebuggerDisplay("{BytesRead}/{TotalBytes}")]
public class ProgressUpdateStatus
internal sealed class ProgressUpdateStatus
{
/// <summary>
/// 构造一个新的进度更新状态

View File

@@ -23,10 +23,16 @@ internal static class Clipboard
{
await ThreadHelper.SwitchToMainThreadAsync();
DataPackageView view = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
string json = await view.GetTextAsync();
await ThreadHelper.SwitchToBackgroundAsync();
return JsonSerializer.Deserialize<T>(json, options);
if (view.Contains(StandardDataFormats.Text))
{
string json = await view.GetTextAsync();
await ThreadHelper.SwitchToBackgroundAsync();
return JsonSerializer.Deserialize<T>(json, options);
}
return null;
}
/// <summary>

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.IO;
/// <summary>
/// 流复制状态
/// </summary>
internal sealed class StreamCopyState
{
/// <summary>
/// 构造一个新的流复制状态
/// </summary>
/// <param name="bytesCopied">已复制字节</param>
/// <param name="totalBytes">总字节数</param>
public StreamCopyState(long bytesCopied, long totalBytes)
{
BytesCopied = bytesCopied;
TotalBytes = totalBytes;
}
/// <summary>
/// 已复制字节
/// </summary>
public long BytesCopied { get; }
/// <summary>
/// 总字节数
/// </summary>
public long TotalBytes { get; }
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO;
namespace Snap.Hutao.Core.IO;
/// <summary>
/// 流复制器
/// </summary>
internal sealed class StreamCopyWorker
{
private readonly Stream source;
private readonly Stream destination;
private readonly long totalBytes;
private readonly int bufferSize;
/// <summary>
/// 创建一个新的流复制器
/// </summary>
/// <param name="source">源</param>
/// <param name="destination">目标</param>
/// <param name="totalBytes">总字节</param>
/// <param name="bufferSize">字节尺寸</param>
public StreamCopyWorker(Stream source, Stream destination, long totalBytes, int bufferSize = 81920)
{
Verify.Operation(source.CanRead, "Source Stream can't read");
Verify.Operation(destination.CanWrite, "Destination Stream can't write");
this.source = source;
this.destination = destination;
this.totalBytes = totalBytes;
this.bufferSize = bufferSize;
}
/// <summary>
/// 异步复制
/// </summary>
/// <param name="progress">进度</param>
/// <returns>任务</returns>
public async Task CopyAsync(IProgress<StreamCopyState> progress)
{
long totalBytesRead = 0;
int bytesRead;
Memory<byte> buffer = new byte[bufferSize];
do
{
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
totalBytesRead += bytesRead;
progress.Report(new(totalBytesRead, totalBytes));
}
while (bytesRead > 0);
}
}

View File

@@ -6,7 +6,8 @@ namespace Snap.Hutao.Core.Json.Annotation;
/// <summary>
/// Json 序列化类型
/// </summary>
public enum JsonSerializeType
[HighQuality]
internal enum JsonSerializeType
{
/// <summary>
/// Int32

View File

@@ -10,6 +10,7 @@ using Snap.Hutao.Service.DailyNote;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Navigation;
using System.Diagnostics;
using System.IO;
using System.Security.Principal;
namespace Snap.Hutao.Core.LifeCycle;
@@ -25,11 +26,6 @@ internal static class Activation
/// </summary>
public const string Action = nameof(Action);
/// <summary>
/// 无操作
/// </summary>
public const string NoAction = "";
/// <summary>
/// Uid
/// </summary>
@@ -146,7 +142,13 @@ internal static class Activation
{
switch (arguments)
{
case NoAction:
case LaunchGame:
{
await HandleLaunchGameActionAsync().ConfigureAwait(false);
break;
}
default:
{
// Increase launch times
LocalSetting.Set(SettingKeys.LaunchTimes, LocalSetting.Get(SettingKeys.LaunchTimes, 0) + 1);
@@ -154,12 +156,6 @@ internal static class Activation
await WaitMainWindowAsync().ConfigureAwait(false);
break;
}
case LaunchGame:
{
await HandleLaunchGameActionAsync().ConfigureAwait(false);
break;
}
}
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Globalization;
namespace Snap.Hutao.Core.Setting;
/// <summary>
/// 本地化
/// </summary>
internal static class Localization
{
/// <summary>
/// 初始化本地化语言
/// </summary>
/// <param name="culture">语言代码</param>
public static void Initialize(string culture)
{
CultureInfo cultureInfo = CultureInfo.CreateSpecificCulture(culture);
CultureInfo.CurrentCulture = cultureInfo;
CultureInfo.CurrentUICulture = cultureInfo;
}
}

View File

@@ -15,18 +15,24 @@
Margin="48,0,0,0"/>
<cwuc:SwitchPresenter x:Name="ContentSwitchPresenter">
<cwuc:Case>
<cwuc:Case.Value>
<x:Boolean>False</x:Boolean>
</cwuc:Case.Value>
<shv:MainView/>
</cwuc:Case>
<cwuc:SwitchPresenter.ContentTransitions>
<TransitionCollection>
<EntranceThemeTransition/>
<ContentThemeTransition/>
</TransitionCollection>
</cwuc:SwitchPresenter.ContentTransitions>
<cwuc:Case>
<cwuc:Case.Value>
<x:Boolean>True</x:Boolean>
</cwuc:Case.Value>
<shv:WelcomeView/>
</cwuc:Case>
<cwuc:Case>
<cwuc:Case.Value>
<x:Boolean>False</x:Boolean>
</cwuc:Case.Value>
<shv:MainView/>
</cwuc:Case>
</cwuc:SwitchPresenter>
</Grid>
</Window>

View File

@@ -1,24 +1,50 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Immutable;
namespace Snap.Hutao.Model.Binding.AvatarProperty;
/// <summary>
/// 角色属性值
/// </summary>
[HighQuality]
internal sealed class AvatarProperty
internal sealed class AvatarProperty : INameIcon
{
private static readonly ImmutableDictionary<FightProperty, Uri> PropertyIcons = new Dictionary<FightProperty, Uri>()
{
[FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_CDReduce.png").ToUri(),
[FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_ChargeEfficiency.png").ToUri(),
[FightProperty.FIGHT_PROP_CRITICAL] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Critical.png").ToUri(),
[FightProperty.FIGHT_PROP_CUR_ATTACK] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_CurAttack.png").ToUri(),
[FightProperty.FIGHT_PROP_CUR_DEFENSE] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_CurDefense.png").ToUri(),
[FightProperty.FIGHT_PROP_ELEMENT_MASTERY] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element.png").ToUri(),
[FightProperty.FIGHT_PROP_ELEC_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Electric.png").ToUri(),
[FightProperty.FIGHT_PROP_FIRE_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Fire.png").ToUri(),
[FightProperty.FIGHT_PROP_GRASS_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Grass.png").ToUri(),
[FightProperty.FIGHT_PROP_ICE_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Ice.png").ToUri(),
[FightProperty.FIGHT_PROP_ROCK_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Rock.png").ToUri(),
[FightProperty.FIGHT_PROP_WATER_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Water.png").ToUri(),
[FightProperty.FIGHT_PROP_WIND_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Element_Wind.png").ToUri(),
[FightProperty.FIGHT_PROP_HEAL_ADD] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_Heal.png").ToUri(),
[FightProperty.FIGHT_PROP_MAX_HP] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_MaxHp.png").ToUri(),
[FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_PhysicalAttackUp.png").ToUri(),
[FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO] = Web.HutaoEndpoints.StaticFile("Property", "UI_Icon_ShieldCostMinus.png").ToUri(),
}.ToImmutableDictionary();
/// <summary>
/// 构造一个新的角色属性值
/// </summary>
/// <param name="property">战斗属性</param>
/// <param name="name">名称</param>
/// <param name="value">白字</param>
/// <param name="addValue">绿字</param>
public AvatarProperty(string name, string value, string? addValue = null)
public AvatarProperty(FightProperty property, string name, string value, string? addValue = null)
{
Name = name;
Value = value;
Icon = PropertyIcons.GetValueOrDefault(property)!;
AddValue = addValue;
}
@@ -27,6 +53,11 @@ internal sealed class AvatarProperty
/// </summary>
public string Name { get; }
/// <summary>
/// 图标
/// </summary>
public Uri Icon { get; }
/// <summary>
/// 白字
/// </summary>

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Binding.AvatarProperty;
/// <summary>
@@ -22,7 +24,7 @@ internal sealed class ReliquarySubProperty
Score = score;
// only 0.25 | 0.50 | 0.75 | 1.00
Opacity = Math.Ceiling(score / 25) / 4;
Opacity = score == 0 ? 0.25 : Math.Ceiling(score / 25) / 4;
}
/// <summary>

View File

@@ -25,22 +25,6 @@ internal sealed class TypedWishSummary : Wish
get => string.Format(SH.ModelBindingGachaTypedWishSummaryMinOrangePullFormat, MinOrangePull);
}
/// <summary>
/// 据上个五星抽数格式化
/// </summary>
public string LastOrangePullFormatted
{
get => string.Format(SH.ModelBindingGachaTypedWishSummaryLastPullFormat, LastOrangePull);
}
/// <summary>
/// 据上个四星抽数格式化
/// </summary>
public string LastPurplePullFormatted
{
get => string.Format(SH.ModelBindingGachaTypedWishSummaryLastPullFormat, LastPurplePull);
}
/// <summary>
/// 据上个五星抽数
/// </summary>

View File

@@ -80,6 +80,11 @@ internal sealed class SettingEntry
/// 启动游戏 目标帧率
/// </summary>
public const string LaunchTargetFps = "Launch.TargetFps";
/// <summary>
/// 语言
/// </summary>
public const string Culture = "Culture";
#endregion
/// <summary>

View File

@@ -28,26 +28,28 @@ internal sealed class PropertyDescriptor : ValueConverter<PropertiesParameters,
/// <summary>
/// 格式化有绿字的角色属性
/// </summary>
/// <param name="property">战斗属性</param>
/// <param name="name">属性名称</param>
/// <param name="method">方法</param>
/// <param name="baseValue">值1</param>
/// <param name="addValue">值2</param>
/// <returns>对2</returns>
public static AvatarProperty FormatAvatarProperty(string name, FormatMethod method, double baseValue, double addValue)
public static AvatarProperty FormatAvatarProperty(FightProperty property, string name, FormatMethod method, double baseValue, double addValue)
{
return new(name, FormatValue(method, baseValue + addValue), $"[{FormatValue(method, baseValue)}+{FormatValue(method, addValue)}]");
return new(property, name, FormatValue(method, baseValue + addValue), $"[{FormatValue(method, baseValue)}+{FormatValue(method, addValue)}]");
}
/// <summary>
/// 格式化无绿字的角色属性
/// </summary>
/// <param name="property">战斗属性</param>
/// <param name="name">属性名称</param>
/// <param name="method">方法</param>
/// <param name="value">值</param>
/// <returns>对2</returns>
public static AvatarProperty FormatAvatarProperty(string name, FormatMethod method, double value)
public static AvatarProperty FormatAvatarProperty(FightProperty property, string name, FormatMethod method, double value)
{
return new(name, FormatValue(method, value));
return new(property, name, FormatValue(method, value));
}
/// <summary>

View File

@@ -1248,6 +1248,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 重命名数据文件夹名称失败 的本地化字符串。
/// </summary>
internal static string ServiceGamePackageRenameDataFolderFailed {
get {
return ResourceManager.GetString("ServiceGamePackageRenameDataFolderFailed", resourceCulture);
}
}
/// <summary>
/// 查找类似 获取 Package Version 的本地化字符串。
/// </summary>
@@ -3768,6 +3777,24 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 设置呈现语言 的本地化字符串。
/// </summary>
internal static string ViewPageSettingApperanceLanguageDescription {
get {
return ResourceManager.GetString("ViewPageSettingApperanceLanguageDescription", resourceCulture);
}
}
/// <summary>
/// 查找类似 语言 的本地化字符串。
/// </summary>
internal static string ViewPageSettingApperanceLanguageHeader {
get {
return ResourceManager.GetString("ViewPageSettingApperanceLanguageHeader", resourceCulture);
}
}
/// <summary>
/// 查找类似 更改窗体的背景材质 的本地化字符串。
/// </summary>
@@ -4002,6 +4029,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 相关链接 的本地化字符串。
/// </summary>
internal static string ViewPageSettingLinks {
get {
return ResourceManager.GetString("ViewPageSettingLinks", resourceCulture);
}
}
/// <summary>
/// 查找类似 重置 的本地化字符串。
/// </summary>
@@ -4083,6 +4119,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 赞助我们 的本地化字符串。
/// </summary>
internal static string ViewPageSettingSponsorNavigate {
get {
return ResourceManager.GetString("ViewPageSettingSponsorNavigate", resourceCulture);
}
}
/// <summary>
/// 查找类似 存储空间 的本地化字符串。
/// </summary>
@@ -4110,6 +4155,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 贡献翻译 的本地化字符串。
/// </summary>
internal static string ViewPageSettingTranslateNavigate {
get {
return ResourceManager.GetString("ViewPageSettingTranslateNavigate", resourceCulture);
}
}
/// <summary>
/// 查找类似 前往商店 的本地化字符串。
/// </summary>

View File

@@ -513,6 +513,9 @@
<data name="ServiceGameLocatorUnityLogGamePathNotFound" xml:space="preserve">
<value>在 Unity 日志文件中找不到游戏路径</value>
</data>
<data name="ServiceGamePackageRenameDataFolderFailed" xml:space="preserve">
<value>重命名数据文件夹名称失败</value>
</data>
<data name="ServiceGamePackageRequestPackageVerion" xml:space="preserve">
<value>获取 Package Version</value>
</data>
@@ -1353,6 +1356,12 @@
<data name="ViewPageSettingApperanceHeader" xml:space="preserve">
<value>外观</value>
</data>
<data name="ViewPageSettingApperanceLanguageDescription" xml:space="preserve">
<value>设置呈现语言</value>
</data>
<data name="ViewPageSettingApperanceLanguageHeader" xml:space="preserve">
<value>语言</value>
</data>
<data name="ViewPageSettingBackdropMaterialDescription" xml:space="preserve">
<value>更改窗体的背景材质</value>
</data>
@@ -1431,6 +1440,9 @@
<data name="ViewPageSettingGameHeader" xml:space="preserve">
<value>游戏</value>
</data>
<data name="ViewPageSettingLinks" xml:space="preserve">
<value>相关链接</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>重置</value>
</data>
@@ -1458,6 +1470,9 @@
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
<value>设置游戏路径时请选择游戏本体YuanShen.exe 或 GenshinImpact.exe而不是启动器launcher.exe</value>
</data>
<data name="ViewPageSettingSponsorNavigate" xml:space="preserve">
<value>赞助我们</value>
</data>
<data name="ViewPageSettingStorageHeader" xml:space="preserve">
<value>存储空间</value>
</data>
@@ -1467,6 +1482,9 @@
<data name="ViewPageSettingStorageSetAction" xml:space="preserve">
<value>更改</value>
</data>
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>贡献翻译</value>
</data>
<data name="ViewPageSettingUpdateCheckAction" xml:space="preserve">
<value>前往商店</value>
</data>

View File

@@ -30,16 +30,20 @@ internal static class SummaryFightPropertyMapHelper
AvatarProperty defProp = GetDefProperty(fightPropMap);
double em = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ELEMENT_MASTERY); // 28
AvatarProperty emProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyEM, FormatMethod.Integer, em);
AvatarProperty emProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(
FightProperty.FIGHT_PROP_ELEMENT_MASTERY, SH.ServiceAvatarInfoPropertyEM, FormatMethod.Integer, em);
double critRate = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_CRITICAL); // 20
AvatarProperty critRateProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyCR, FormatMethod.Percent, critRate);
AvatarProperty critRateProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(
FightProperty.FIGHT_PROP_CRITICAL, SH.ServiceAvatarInfoPropertyCR, FormatMethod.Percent, critRate);
double critDMG = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_CRITICAL_HURT); // 22
AvatarProperty critDMGProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyCDmg, FormatMethod.Percent, critDMG);
AvatarProperty critDMGProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(
FightProperty.FIGHT_PROP_CRITICAL_HURT, SH.ServiceAvatarInfoPropertyCDmg, FormatMethod.Percent, critDMG);
double chargeEff = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY); // 23
AvatarProperty chargeEffProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyCE, FormatMethod.Percent, chargeEff);
AvatarProperty chargeEffProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(
FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, SH.ServiceAvatarInfoPropertyCE, FormatMethod.Percent, chargeEff);
List<AvatarProperty> properties = new(9) { hpProp, atkProp, defProp, emProp, critRateProp, critDMGProp, chargeEffProp };
@@ -50,7 +54,8 @@ internal static class SummaryFightPropertyMapHelper
double value = fightPropMap[bonusProperty];
if (value > 0)
{
AvatarProperty bonusProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(bonusProperty.GetLocalizedDescription(), FormatMethod.Percent, value);
AvatarProperty bonusProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(
bonusProperty, bonusProperty.GetLocalizedDescription(), FormatMethod.Percent, value);
properties.Add(bonusProp);
}
}
@@ -61,7 +66,8 @@ internal static class SummaryFightPropertyMapHelper
if (addValue > 0)
{
string description = FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT.GetLocalizedDescription();
AvatarProperty physicalBonusProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(description, FormatMethod.Percent, addValue);
AvatarProperty physicalBonusProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(
FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, description, FormatMethod.Percent, addValue);
properties.Add(physicalBonusProp);
}
}
@@ -69,36 +75,39 @@ internal static class SummaryFightPropertyMapHelper
return properties;
}
private static AvatarProperty GetDefProperty(Dictionary<FightProperty, double> fightPropMap)
{
double baseDef = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_DEFENSE); // 7
double def = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE); // 8
double defPercent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE_PERCENT); // 9
double defAdd = def + (baseDef * defPercent);
AvatarProperty defProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyDef, FormatMethod.Integer, baseDef, defAdd);
return defProp;
}
private static AvatarProperty GetAtkProperty(Dictionary<FightProperty, double> fightPropMap)
{
double baseAtk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_ATTACK); // 4
double atk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK); // 5
double atkPrecent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK_PERCENT); // 6
double atkAdd = atk + (baseAtk * atkPrecent);
AvatarProperty atkProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyAtk, FormatMethod.Integer, baseAtk, atkAdd);
return atkProp;
}
private static AvatarProperty GetHpProperty(Dictionary<FightProperty, double> fightPropMap)
{
double baseHp = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_HP); // 1
double hp = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_HP); // 2
double hpPercent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_HP_PERCENT); // 3
double hpAdd = hp + (baseHp * hpPercent);
AvatarProperty hpProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(SH.ServiceAvatarInfoPropertyHp, FormatMethod.Integer, baseHp, hpAdd);
AvatarProperty hpProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(
FightProperty.FIGHT_PROP_MAX_HP, SH.ServiceAvatarInfoPropertyHp, FormatMethod.Integer, baseHp, hpAdd);
return hpProp;
}
private static AvatarProperty GetAtkProperty(Dictionary<FightProperty, double> fightPropMap)
{
double baseAtk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_ATTACK); // 4
double atk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK); // 5
double atkPrecent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK_PERCENT); // 6
double atkAdd = atk + (baseAtk * atkPrecent);
AvatarProperty atkProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(
FightProperty.FIGHT_PROP_CUR_ATTACK, SH.ServiceAvatarInfoPropertyAtk, FormatMethod.Integer, baseAtk, atkAdd);
return atkProp;
}
private static AvatarProperty GetDefProperty(Dictionary<FightProperty, double> fightPropMap)
{
double baseDef = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_DEFENSE); // 7
double def = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE); // 8
double defPercent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE_PERCENT); // 9
double defAdd = def + (baseDef * defPercent);
AvatarProperty defProp = Model.Metadata.Converter.PropertyDescriptor.FormatAvatarProperty(
FightProperty.FIGHT_PROP_CUR_DEFENSE, SH.ServiceAvatarInfoPropertyDef, FormatMethod.Integer, baseDef, defAdd);
return defProp;
}
private static FightProperty GetBonusFightProperty(IDictionary<FightProperty, double> fightPropMap)
{
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY))

View File

@@ -45,7 +45,7 @@ internal sealed class SummaryReliquaryFactory
public PropertyReliquary CreateReliquary()
{
MetadataReliquary reliquary = metadataContext.Reliquaries.Single(r => r.Ids.Contains(equip.ItemId));
List<ReliquarySubProperty> subProperty = equip.Reliquary!.AppendPropIdList.EmptyIfNull().Select(CreateSubProperty).ToList();
List<ReliquarySubProperty> subProperty = equip.Reliquary!.AppendPropIdList.EmptyIfNull().SelectList(CreateSubProperty);
int affixCount = GetSecondaryAffixCount(reliquary);
if (subProperty.Count == 0)
@@ -67,7 +67,7 @@ internal sealed class SummaryReliquaryFactory
List<ReliquarySubProperty> primary = new(span[..^affixCount].ToArray());
List<ReliquarySubProperty> secondary = new(span[^affixCount..].ToArray());
List<ReliquarySubProperty> composed = equip.Flat.ReliquarySubstats!.Select(CreateComposedSubProperty).ToList();
List<ReliquarySubProperty> composed = equip.Flat.ReliquarySubstats!.SelectList(CreateComposedSubProperty);
ReliquaryLevel relicLevel = metadataContext.ReliqueryLevels.Single(r => r.Level == equip.Reliquary!.Level && r.Quality == reliquary.RankLevel);
FightProperty property = metadataContext.IdRelicMainPropMap[equip.Reliquary.MainPropId];

View File

@@ -9,18 +9,18 @@ namespace Snap.Hutao.Service.Game.Package;
[HighQuality]
internal enum ItemOperationType
{
/// <summary>
/// 添加
/// </summary>
Add,
/// <summary>
/// 删除
/// </summary>
Remove,
Remove = 0,
/// <summary>
/// 替换
/// </summary>
Replace,
Replace = 1,
/// <summary>
/// 添加
/// </summary>
Add = 2,
}

View File

@@ -48,53 +48,42 @@ internal sealed class PackageConverter
{
await ThreadHelper.SwitchToBackgroundAsync();
string scatteredFilesUrl = gameResouce.Game.Latest.DecompressedPath;
Uri pkgVersionUri = new($"{scatteredFilesUrl}/pkg_version");
Uri pkgVersionUri = $"{scatteredFilesUrl}/pkg_version".ToUri();
ConvertDirection direction = targetScheme.IsOversea ? ConvertDirection.ChineseToOversea : ConvertDirection.OverseaToChinese;
progress.Report(new(SH.ServiceGamePackageRequestPackageVerion));
Dictionary<string, VersionItem> remoteItems = default!;
Dictionary<string, VersionItem> remoteItems;
try
{
using (Stream remoteSteam = await httpClient.GetStreamAsync(pkgVersionUri).ConfigureAwait(false))
{
remoteItems = await GetVersionItemsAsync(remoteSteam).ConfigureAwait(false);
remoteItems = await GetRemoteVersionItemsAsync(remoteSteam).ConfigureAwait(false);
}
}
catch (IOException ex)
{
ThrowHelper.PackageConvert(SH.ServiceGamePackageRequestPackageVerionFailed, ex);
throw ThrowHelper.PackageConvert(SH.ServiceGamePackageRequestPackageVerionFailed, ex);
}
Dictionary<string, VersionItem> localItems;
using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, "pkg_version")))
{
localItems = await GetVersionItemsAsync(localSteam, direction, ConvertRemoteName).ConfigureAwait(false);
localItems = await GetLocalVersionItemsAsync(localSteam, direction, ConvertRemoteName).ConfigureAwait(false);
}
IEnumerable<ItemOperationInfo> diffOperations = GetItemOperationInfos(remoteItems, localItems);
IEnumerable<ItemOperationInfo> diffOperations = GetItemOperationInfos(remoteItems, localItems).OrderBy(i => (int)i.Type);
return await ReplaceGameResourceAsync(diffOperations, gameFolder, scatteredFilesUrl, direction, progress).ConfigureAwait(false);
}
/// <summary>
/// 检查过时文件与Sdk
/// 只在国服环境有效
/// </summary>
/// <param name="resource">游戏资源</param>
/// <param name="gameFolder">游戏文件夹</param>
/// <returns>任务</returns>
public async Task EnsureDeprecatedFilesAndSdkAsync(GameResource resource, string gameFolder)
{
if (resource.DeprecatedFiles != null)
{
foreach (NameMd5 file in resource.DeprecatedFiles)
{
string filePath = Path.Combine(gameFolder, file.Name);
if (File.Exists(filePath))
{
File.Move(filePath, $"{filePath}.backup");
}
}
}
string sdkDllBackup = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll.backup");
string sdkDll = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll");
string sdkVersionBackup = Path.Combine(gameFolder, YuanShenData, "sdk_pkg_version.backup");
@@ -103,6 +92,7 @@ internal sealed class PackageConverter
// Only bilibili's sdk is not null
if (resource.Sdk != null)
{
// TODO: verify sdk md5
if (File.Exists(sdkDllBackup) && File.Exists(sdkVersionBackup))
{
FileOperation.Move(sdkDllBackup, sdkDll, false);
@@ -133,6 +123,15 @@ internal sealed class PackageConverter
FileOperation.Move(sdkDll, sdkDllBackup, true);
FileOperation.Move(sdkVersion, sdkVersionBackup, true);
}
if (resource.DeprecatedFiles != null)
{
foreach (NameMd5 file in resource.DeprecatedFiles)
{
string filePath = Path.Combine(gameFolder, file.Name);
FileOperation.Move(filePath, $"{filePath}.backup", true);
}
}
}
private static string ConvertRemoteName(string remoteName, ConvertDirection direction)
@@ -216,6 +215,7 @@ internal sealed class PackageConverter
long totalBytesRead = 0;
int bytesRead;
Memory<byte> buffer = new byte[bufferSize];
do
{
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
@@ -223,11 +223,6 @@ internal sealed class PackageConverter
totalBytesRead += bytesRead;
progress.Report(new(name, totalBytesRead, totalBytes));
if (bytesRead <= 0)
{
break;
}
}
while (bytesRead > 0);
}
@@ -239,11 +234,11 @@ internal sealed class PackageConverter
{
RenameDataFolder(gameFolder, direction);
}
catch (IOException)
catch (IOException ex)
{
// Access to the path is denied.
// When user install the game in special folder like 'Program Files'
return false;
throw ThrowHelper.GameFileOperation(SH.ServiceGamePackageRenameDataFolderFailed, ex);
}
// Cache folder
@@ -260,20 +255,16 @@ internal sealed class PackageConverter
switch (info.Type)
{
case ItemOperationType.Add:
await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false);
break;
case ItemOperationType.Replace:
{
MoveToCache(moveToFilePath, targetFilePath);
await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false);
break;
}
case ItemOperationType.Remove:
MoveToCache(moveToFilePath, targetFilePath);
break;
case ItemOperationType.Replace:
MoveToCache(moveToFilePath, targetFilePath);
await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false);
break;
case ItemOperationType.Add:
await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info, progress).ConfigureAwait(false);
break;
default:
break;
}
@@ -316,7 +307,6 @@ internal sealed class PackageConverter
try
{
await CopyToWithProgressAsync(webStream, fileStream, info.Target, totalBytes, progress).ConfigureAwait(false);
fileStream.Seek(0, SeekOrigin.Begin);
string remoteMd5 = await Digest.GetStreamMD5Async(fileStream).ConfigureAwait(false);
if (info.Md5 == remoteMd5.ToLowerInvariant())
{
@@ -359,7 +349,7 @@ internal sealed class PackageConverter
}
}
private async Task<Dictionary<string, VersionItem>> GetVersionItemsAsync(Stream stream)
private async Task<Dictionary<string, VersionItem>> GetRemoteVersionItemsAsync(Stream stream)
{
Dictionary<string, VersionItem> results = new();
using (StreamReader reader = new(stream))
@@ -377,7 +367,7 @@ internal sealed class PackageConverter
return results;
}
private async Task<Dictionary<string, VersionItem>> GetVersionItemsAsync(Stream stream, ConvertDirection direction, Func<string, ConvertDirection, string> nameConverter)
private async Task<Dictionary<string, VersionItem>> GetLocalVersionItemsAsync(Stream stream, ConvertDirection direction, Func<string, ConvertDirection, string> nameConverter)
{
Dictionary<string, VersionItem> results = new();

View File

@@ -44,14 +44,12 @@ internal static class RegistryInterop
Set-ItemProperty -Path '{path}' -Name '{SdkKey}' -Value $value -Force;
""";
string psExecutablePath = @"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe";
ProcessStartInfo startInfo = new()
{
Arguments = command,
WorkingDirectory = Path.GetDirectoryName(psExecutablePath),
WorkingDirectory = Path.GetDirectoryName(PsExecutablePath),
CreateNoWindow = true,
FileName = psExecutablePath,
FileName = PsExecutablePath,
};
try

View File

@@ -6,7 +6,8 @@ namespace Snap.Hutao.Service.User;
/// <summary>
/// 用户添加操作结果
/// </summary>
public enum UserOptionResult
[HighQuality]
internal enum UserOptionResult
{
/// <summary>
/// 添加成功

View File

@@ -160,15 +160,15 @@
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.5.10-alpha">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.5.22">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -184,7 +184,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="TaskScheduler" Version="2.10.1" />
<PackageReference Include="WinUICommunity.SettingsUI" Version="3.0.2" />
<PackageReference Include="WinUICommunity.SettingsUI" Version="3.0.4" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>

View File

@@ -4,7 +4,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control;
using Snap.Hutao.Model.Metadata.Avatar;
using System.Collections;
namespace Snap.Hutao.View.Control;
@@ -14,7 +14,7 @@ namespace Snap.Hutao.View.Control;
[HighQuality]
internal sealed partial class SkillPivot : UserControl
{
private static readonly DependencyProperty SkillsProperty = Property<SkillPivot>.Depend<List<Skill>>(nameof(Skills));
private static readonly DependencyProperty SkillsProperty = Property<SkillPivot>.Depend<IList>(nameof(Skills));
private static readonly DependencyProperty SelectedProperty = Property<SkillPivot>.Depend<object>(nameof(Selected));
private static readonly DependencyProperty ItemTemplateProperty = Property<SkillPivot>.Depend<DataTemplate>(nameof(ItemTemplate));
@@ -29,9 +29,9 @@ internal sealed partial class SkillPivot : UserControl
/// <summary>
/// 技能列表
/// </summary>
public List<Skill> Skills
public IList Skills
{
get => (List<Skill>)GetValue(SkillsProperty);
get => (IList)GetValue(SkillsProperty);
set => SetValue(SkillsProperty, value);
}
@@ -52,4 +52,4 @@ internal sealed partial class SkillPivot : UserControl
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
}
}

View File

@@ -119,11 +119,21 @@
</Expander.Resources>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{Binding Name}"/>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"/>
<StackPanel
Grid.Column="1"
HorizontalAlignment="Right"
Orientation="Horizontal">
<TextBlock
Margin="0,0,6,0"
VerticalAlignment="Center"

View File

@@ -18,7 +18,6 @@ namespace Snap.Hutao.View.Dialog;
internal sealed partial class CommunityGameRecordDialog : ContentDialog
{
private readonly IServiceScope scope;
[SuppressMessage("", "IDE0052")]
private MiHoYoJSInterface? jsInterface;
/// <summary>

View File

@@ -254,6 +254,7 @@
MaxWidth="800"
HorizontalAlignment="Left"
Background="Transparent">
<!-- 卡片面板 -->
<Border Margin="16" Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.RowDefinitions>
@@ -537,41 +538,63 @@
</ItemsControl>
</Grid>
</Border>
<!-- 角色属性 -->
<Expander
Margin="16,0,16,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Background="{x:Null}"
Header="{shcm:ResourceString Name=ViewPageAvatarPropertyHeader}">
<ItemsControl ItemsSource="{Binding SelectedAvatar.Properties}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<wsc:Setting
Margin="0,2,0,0"
BorderBrush="{x:Null}"
BorderThickness="0"
Header="{Binding Key}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Right"
Text="{Binding Value1}"/>
<TextBlock
Grid.Column="1"
Margin="6,0,0,0"
HorizontalAlignment="Left"
Foreground="{StaticResource AvatarPropertyAddValueBrush}"
Text="{Binding Value2}"/>
</Grid>
</wsc:Setting>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}" CornerRadius="0,0,4,4">
<ItemsControl Margin="0,0,0,-2" ItemsSource="{Binding SelectedAvatar.Properties}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="16,8,16,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="108"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<shci:MonoChrome
Width="16"
Height="16"
Source="{Binding Icon}"/>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="16,0,0,0"
HorizontalAlignment="Left"
Text="{Binding Name}"/>
<TextBlock
Grid.Row="0"
Grid.Column="2"
HorizontalAlignment="Right"
Text="{Binding Value}"/>
<TextBlock
Grid.Row="0"
Grid.Column="3"
Margin="8,0,0,0"
HorizontalAlignment="Left"
Foreground="{StaticResource AvatarPropertyAddValueBrush}"
Text="{Binding AddValue}"/>
<MenuFlyoutSeparator
Grid.Row="1"
Grid.ColumnSpan="4"
Margin="4,8,4,0"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Expander>
<!-- 圣遗物 -->
<ItemsControl Margin="16,16,16,0" ItemsSource="{Binding SelectedAvatar.Reliquaries}">
<ItemsControl.ItemTemplate>
<DataTemplate>
@@ -581,7 +604,7 @@
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch">
<Expander.Header>
<Grid Margin="0,16">
<Grid Margin="0,8,8,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>

View File

@@ -2,6 +2,7 @@
x:Class="Snap.Hutao.View.Page.SettingPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shc="using:Snap.Hutao.Control"
@@ -16,7 +17,7 @@
<Setter Property="MinWidth" Value="160"/>
</Style>
<Style BasedOn="{StaticResource HyperlinkButtonStyle}" TargetType="HyperlinkButton">
<Setter Property="MinWidth" Value="160"/>
<Setter Property="MinWidth" Value="120"/>
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}"/>
</Style>
<Style BasedOn="{StaticResource DefaultComboBoxStyle}" TargetType="ComboBox">
@@ -46,8 +47,39 @@
</Border>
<Grid Grid.Column="1" Margin="16,0,0,0">
<TextBlock Text="Copyright © 2022 - 2023 DGP Studio. All Rights Reserved." TextWrapping="Wrap"/>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Text="Copyright © 2022 - 2023 DGP Studio. All Rights Reserved."
TextWrapping="Wrap"/>
<StackPanel
Grid.Row="1"
VerticalAlignment="Bottom"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{shcm:ResourceString Name=ViewPageSettingLinks}"/>
<HyperlinkButton
Margin="12,0,0,0"
Command="{Binding UpdateCheckCommand}"
Content="{shcm:ResourceString Name=ViewPageSettingUpdateCheckAction}"/>
<HyperlinkButton
Margin="12,0,0,0"
Content="{shcm:ResourceString Name=ViewPageSettingFeedbackNavigate}"
NavigateUri="{StaticResource DocumentLink_BugReport}"/>
<HyperlinkButton
Margin="12,0,0,0"
Content="{shcm:ResourceString Name=ViewPageSettingTranslateNavigate}"
NavigateUri="{StaticResource DocumentLink_Translate}"/>
<HyperlinkButton
Margin="12,0,0,0"
Content="{shcm:ResourceString Name=ViewPageSettingSponsorNavigate}"
NavigateUri="{StaticResource Sponsor_Afadian}"/>
</StackPanel>
</Grid>
</Grid>
<wsc:Setting
Description="{Binding AppVersion}"
@@ -65,23 +97,19 @@
Description="{Binding WebView2Version}"
Header="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"
Icon="&#xECAA;"/>
<wsc:Setting
Description="{shcm:ResourceString Name=ViewPageSettingFeedbackDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingFeedbackHeader}"
Icon="&#xED15;">
<HyperlinkButton Content="{shcm:ResourceString Name=ViewPageSettingFeedbackNavigate}" NavigateUri="{StaticResource DocumentLink_BugReport}"/>
</wsc:Setting>
<wsc:Setting
Description="{shcm:ResourceString Name=ViewPageSettingUpdateCheckDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingUpdateCheckHeader}"
Icon="&#xE117;">
<wsc:Setting.ActionContent>
<HyperlinkButton Command="{Binding UpdateCheckCommand}" Content="{shcm:ResourceString Name=ViewPageSettingUpdateCheckAction}"/>
</wsc:Setting.ActionContent>
</wsc:Setting>
</wsc:SettingsGroup>
<wsc:SettingsGroup Header="{shcm:ResourceString Name=ViewPageSettingApperanceHeader}">
<wsc:Setting
Description="{shcm:ResourceString Name=ViewPageSettingApperanceLanguageDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingApperanceLanguageHeader}"
Icon="&#xF2B7;">
<ComboBox
MinWidth="180"
DisplayMemberPath="Name"
ItemsSource="{Binding Cultures}"
SelectedItem="{Binding SelectedCulture, Mode=TwoWay}"/>
</wsc:Setting>
<wsc:Setting
Description="{shcm:ResourceString Name=ViewPageSettingBackdropMaterialDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingBackdropMaterialHeader}"

View File

@@ -26,7 +26,7 @@
Grid.Row="1"
DisplayMode="Inline"
IsPaneOpen="True"
OpenPaneLength="96"
OpenPaneLength="120"
PaneBackground="Transparent"
Visibility="{Binding SpiralAbyssView, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<SplitView.Pane>

View File

@@ -32,6 +32,16 @@
<wsc:Setting Header="DownloadStaticFileTest">
<Button Command="{Binding DownloadStaticFileCommand}" Content="Download"/>
</wsc:Setting>
<wsc:Setting Header="RestartTest">
<StackPanel Orientation="Horizontal">
<ToggleSwitch Name="ElevatedSwitch" Style="{StaticResource DefaultToggleSwitchStyle}"/>
<Button
Command="{Binding RestartAppCommand}"
CommandParameter="{Binding ElementName=ElevatedSwitch, Path=IsOn}"
Content="Restart"/>
</StackPanel>
</wsc:Setting>
</wsc:SettingsGroup>
</ScrollViewer>
</shc:ScopedPage>

View File

@@ -20,11 +20,27 @@
<StackPanel>
<Button
Grid.Column="2"
MaxHeight="40"
Margin="4,4,4,6"
Background="Transparent"
BorderBrush="{x:Null}">
<Button.Resources>
<shc:BindingProxy x:Key="ViewModelBindingProxy" DataContext="{Binding}"/>
<StaticResource x:Key="ButtonBackground" ResourceKey="NavigationViewItemBackground"/>
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="NavigationViewItemBackgroundDisabled"/>
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="NavigationViewItemBackgroundPointerOver"/>
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="NavigationViewItemBackgroundPressed"/>
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="NavigationViewItemBorderBrush"/>
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="NavigationViewItemBorderBrushDisabled"/>
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="NavigationViewItemBorderBrushPointerOver"/>
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="NavigationViewItemBorderBrushPressed"/>
<StaticResource x:Key="ButtonForeground" ResourceKey="NavigationViewItemForeground"/>
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="NavigationViewItemForegroundDisabled"/>
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="NavigationViewItemForegroundPointerOver"/>
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="NavigationViewItemForegroundPressed"/>
</Button.Resources>
<Button.Content>
<Grid>
@@ -217,6 +233,7 @@
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
</Button.Style>
</Button>

View File

@@ -42,18 +42,20 @@
</ItemsControl.ItemContainerTransitions>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,8,0,0">
<TextBlock Text="{Binding DisplayName}"/>
<ProgressBar
Width="240"
Margin="0,4,0,0"
Maximum="1"
Value="{Binding ProgressValue}"/>
<TextBlock
Opacity="0.6"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Description}"/>
</StackPanel>
<Border Style="{StaticResource BorderCardStyle}">
<StackPanel Margin="0,8,0,0">
<TextBlock Text="{Binding DisplayName}"/>
<ProgressBar
Width="240"
Margin="0,4,0,0"
Maximum="1"
Value="{Binding ProgressValue}"/>
<TextBlock
Opacity="0.6"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Description}"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

View File

@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service.Abstraction;
using Windows.Storage;
@@ -65,9 +66,7 @@ internal sealed class ExperimentalFeaturesViewModel : ObservableObject
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users.ExecuteDeleteAsync().ConfigureAwait(false);
IInfoBarService infoBarService = scope.ServiceProvider.GetRequiredService<IInfoBarService>();
infoBarService.Success(SH.ViewModelExperimentalDeleteUserSuccess);
AppInstance.Restart(string.Empty);
}
}
}

View File

@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.Setting;
@@ -17,6 +18,7 @@ using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.View.Dialog;
using System.Globalization;
using System.IO;
using Windows.Storage.Pickers;
@@ -42,9 +44,18 @@ internal sealed class SettingViewModel : Abstraction.ViewModel
new("MicaAlt", BackdropType.MicaAlt),
};
private readonly List<NameValue<string>> cultures = new()
{
new("简体中文", "zh-CN"),
new("繁體中文", "zh-TW"),
new("English (United States)", "en-US"),
new("한국인", "ko-KR"),
};
private bool isEmptyHistoryWishVisible;
private string gamePath;
private NameValue<BackdropType> selectedBackdropType;
private NameValue<string>? selectedCulture;
/// <summary>
/// 构造一个新的设置视图模型
@@ -61,6 +72,9 @@ internal sealed class SettingViewModel : Abstraction.ViewModel
isEmptyHistoryWishVisibleEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.IsEmptyHistoryWishVisible, Core.StringLiterals.False);
IsEmptyHistoryWishVisible = bool.Parse(isEmptyHistoryWishVisibleEntry.Value!);
string? cultureName = appDbContext.Settings.SingleOrAdd(SettingEntry.Culture, CultureInfo.CurrentCulture.Name).Value;
selectedCulture = cultures.FirstOrDefault(c => c.Value == cultureName);
selectedBackdropTypeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.SystemBackdropType, BackdropType.Mica.ToString());
BackdropType type = Enum.Parse<BackdropType>(selectedBackdropTypeEntry.Value!);
@@ -154,6 +168,29 @@ internal sealed class SettingViewModel : Abstraction.ViewModel
}
}
/// <summary>
/// 语言
/// </summary>
public List<NameValue<string>> Cultures { get => cultures; }
/// <summary>
/// 选中的语言
/// </summary>
public NameValue<string>? SelectedCulture
{
get => selectedCulture;
set
{
if (SetProperty(ref selectedCulture, value))
{
SettingEntry entry = appDbContext.Settings.SingleOrAdd(SettingEntry.Culture, CultureInfo.CurrentCulture.Name);
entry.Value = selectedCulture.Value;
appDbContext.Settings.UpdateAndSave(entry);
AppInstance.Restart(string.Empty);
}
}
}
/// <summary>
/// 实验性功能
/// </summary>
@@ -271,6 +308,6 @@ internal sealed class SettingViewModel : Abstraction.ViewModel
private void ResetStaticResource()
{
StaticResource.UnfulfillAllContracts();
serviceProvider.GetRequiredService<IInfoBarService>().Success(SH.ViewPageSettingResetSuccessMessage);
AppInstance.Restart(string.Empty);
}
}

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.Bits;
using Snap.Hutao.View.Dialog;
@@ -29,6 +30,7 @@ internal sealed class TestViewModel : Abstraction.ViewModel
ShowCommunityGameRecordDialogCommand = new AsyncRelayCommand(ShowCommunityGameRecordDialogAsync);
ShowAdoptCalculatorDialogCommand = new AsyncRelayCommand(ShowAdoptCalculatorDialogAsync);
DownloadStaticFileCommand = new AsyncRelayCommand(DownloadStaticFileAsync);
RestartAppCommand = new RelayCommand<bool>(RestartApp);
}
/// <summary>
@@ -46,6 +48,11 @@ internal sealed class TestViewModel : Abstraction.ViewModel
/// </summary>
public ICommand DownloadStaticFileCommand { get; }
/// <summary>
/// 重启命令
/// </summary>
public ICommand RestartAppCommand { get; }
private async Task ShowCommunityGameRecordDialogAsync()
{
// ContentDialog must be created by main thread.
@@ -80,4 +87,9 @@ internal sealed class TestViewModel : Abstraction.ViewModel
}
}
}
private void RestartApp(bool elevated)
{
AppInstance.Restart(string.Empty);
}
}

View File

@@ -57,12 +57,11 @@ internal sealed class WelcomeViewModel : ObservableObject
// Cancel all previous created jobs
serviceProvider.GetRequiredService<BitsManager>().CancelAllJobs();
await Task.WhenAll(downloadSummaries.Select(async downloadTask =>
await Parallel.ForEachAsync(downloadSummaries, async (summary, token) =>
{
await downloadTask.DownloadAndExtractAsync().ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
DownloadSummaries.Remove(downloadTask);
})).ConfigureAwait(true);
await summary.DownloadAndExtractAsync().ConfigureAwait(false);
ThreadHelper.InvokeOnMainThread(() => DownloadSummaries.Remove(summary));
}).ConfigureAwait(true);
serviceProvider.GetRequiredService<IMessenger>().Send(new Message.WelcomeStateCompleteMessage());
StaticResource.FulfillAllContracts();
@@ -123,7 +122,7 @@ internal sealed class WelcomeViewModel : ObservableObject
/// 下载信息
/// </summary>
[SuppressMessage("", "CA1067")]
public class DownloadSummary : ObservableObject, IEquatable<DownloadSummary>
internal sealed class DownloadSummary : ObservableObject, IEquatable<DownloadSummary>
{
private readonly IServiceProvider serviceProvider;
private readonly BitsManager bitsManager;

View File

@@ -202,6 +202,7 @@ internal class MiHoYoJSInterface
await userService.RefreshCookieTokenAsync(user).ConfigureAwait(false);
}
await ThreadHelper.SwitchToMainThreadAsync();
webView.SetCookie(user.CookieToken, user.Ltoken);
return new() { Data = new() { [Cookie.COOKIE_TOKEN] = user.CookieToken![Cookie.COOKIE_TOKEN] } };
}

View File

@@ -44,7 +44,7 @@ internal sealed class Flat
/// List of Artifact Substats
/// </summary>
[JsonPropertyName("reliquarySubstats")]
public IList<ReliquarySubstat>? ReliquarySubstats { get; set; }
public List<ReliquarySubstat>? ReliquarySubstats { get; set; }
/// <summary>
/// 物品类型

View File

@@ -42,9 +42,7 @@ internal static class StructMarshal
public static unsafe Windows.UI.Color Color(uint value)
{
Unsafe.SkipInit(out Windows.UI.Color color);
uint reversed = BinaryPrimitives.ReverseEndianness(value);
Unsafe.WriteUnaligned(&color, reversed);
*(uint*)&color = BinaryPrimitives.ReverseEndianness(value); // Unsafe.WriteUnaligned(&color, reversed);
return color;
}