mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
fix translation
This commit is contained in:
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -14,7 +14,7 @@ body:
|
||||
id: back
|
||||
attributes:
|
||||
label: 背景与动机
|
||||
description: 添加此功能的理由
|
||||
description: 添加此功能的理由,如果你想要实现多个功能,请分别发起多个单独的 Issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
AssociatedObject.SizeChanged -= OnSizeChanged;
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
|
||||
@@ -43,6 +43,7 @@ internal class AutoWidthBehavior : BehaviorBase<FrameworkElement>
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
AssociatedObject.SizeChanged -= OnSizeChanged;
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
|
||||
@@ -30,6 +30,15 @@ internal class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBa
|
||||
AssociatedObject.DropDownClosed += OnDropDownClosed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
AssociatedObject.DropDownOpened -= OnDropDownOpened;
|
||||
AssociatedObject.DropDownClosed -= OnDropDownClosed;
|
||||
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
private void OnDropDownOpened(object? sender, object e)
|
||||
{
|
||||
messenger.Send(new Message.FlyoutOpenCloseMessage(true));
|
||||
|
||||
@@ -40,7 +40,5 @@ internal class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
|
||||
{
|
||||
Command?.Execute(CommandParameter);
|
||||
}
|
||||
|
||||
base.OnAssociatedObjectLoaded();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
/// <summary>
|
||||
/// 运行环境异常
|
||||
/// 用户的计算机中的某些设置不符合要求
|
||||
/// </summary>
|
||||
internal class RuntimeEnvironmentException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的运行环境异常
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="innerException">内部错误</param>
|
||||
public RuntimeEnvironmentException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ internal static class ThrowHelper
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <exception cref="OperationCanceledException">操作取消异常</exception>
|
||||
/// <returns>nothing</returns>
|
||||
/// <exception cref="OperationCanceledException">操作取消异常</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? inner)
|
||||
@@ -45,12 +45,26 @@ internal static class ThrowHelper
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <exception cref="UserdataCorruptedException">数据损坏</exception>
|
||||
/// <returns>nothing</returns>
|
||||
/// <exception cref="UserdataCorruptedException">数据损坏</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static UserdataCorruptedException UserdataCorrupted(string message, Exception inner)
|
||||
{
|
||||
throw new UserdataCorruptedException(message, inner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行环境异常
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="inner">内部错误</param>
|
||||
/// <returns>nothing</returns>
|
||||
/// <exception cref="RuntimeEnvironmentException">环境异常</exception>
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static RuntimeEnvironmentException RuntimeEnvironment(string message, Exception inner)
|
||||
{
|
||||
throw new RuntimeEnvironmentException(message, inner);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Windows.Storage.Pickers;
|
||||
using WinRT.Interop;
|
||||
|
||||
@@ -37,8 +38,12 @@ internal class PickerFactory : IPickerFactory
|
||||
picker.FileTypeFilter.Add(type);
|
||||
}
|
||||
|
||||
// below Windows 11
|
||||
if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 13))
|
||||
{
|
||||
// https://github.com/microsoft/WindowsAppSDK/issues/2931
|
||||
picker.FileTypeFilter.Add(AnyType);
|
||||
}
|
||||
|
||||
return picker;
|
||||
}
|
||||
@@ -52,7 +57,16 @@ internal class PickerFactory : IPickerFactory
|
||||
/// <inheritdoc/>
|
||||
public FolderPicker GetFolderPicker()
|
||||
{
|
||||
return GetInitializedPicker<FolderPicker>();
|
||||
FolderPicker picker = GetInitializedPicker<FolderPicker>();
|
||||
|
||||
// below Windows 11
|
||||
if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 13))
|
||||
{
|
||||
// https://github.com/microsoft/WindowsAppSDK/issues/2931
|
||||
picker.FileTypeFilter.Add(AnyType);
|
||||
}
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
private T GetInitializedPicker<T>()
|
||||
|
||||
@@ -18,11 +18,13 @@ internal class InventoryItem : ObservableObject
|
||||
/// </summary>
|
||||
/// <param name="inner">元数据</param>
|
||||
/// <param name="entity">实体</param>
|
||||
public InventoryItem(Material inner, Entity.InventoryItem entity)
|
||||
/// <param name="saveCommand">保存命令</param>
|
||||
public InventoryItem(Material inner, Entity.InventoryItem entity, ICommand saveCommand)
|
||||
{
|
||||
Entity = entity;
|
||||
Inner = inner;
|
||||
count = entity.Count;
|
||||
SaveCountCommand = saveCommand;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,6 +37,11 @@ internal class InventoryItem : ObservableObject
|
||||
/// </summary>
|
||||
public Material Inner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 保存个数命令
|
||||
/// </summary>
|
||||
public ICommand? SaveCountCommand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 个数
|
||||
/// </summary>
|
||||
@@ -45,7 +52,7 @@ internal class InventoryItem : ObservableObject
|
||||
if (SetProperty(ref count, value))
|
||||
{
|
||||
Entity.Count = value;
|
||||
Ioc.Default.GetRequiredService<Service.Cultivation.ICultivationService>().SaveInventoryItem(this);
|
||||
SaveCountCommand?.Execute(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class UIGF
|
||||
{
|
||||
foreach (UIGFItem item in List)
|
||||
{
|
||||
if (item.ItemType != "角色" || item.ItemType != "武器")
|
||||
if (item.ItemType != "角色" && item.ItemType != "武器")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<DeveloperAccountType>MSA</DeveloperAccountType>
|
||||
<GeneratePackageHash>http://www.w3.org/2001/04/xmlenc#sha256</GeneratePackageHash>
|
||||
<SupportedLocales>
|
||||
<Language Code="en-us" InMinimumRequirementSet="true" />
|
||||
<Language Code="zh-cn" InMinimumRequirementSet="true" />
|
||||
</SupportedLocales>
|
||||
<ProductReservedInfo>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutao"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.4.11.0" />
|
||||
Version="1.4.14.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao</DisplayName>
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 用户数据已损坏: {0} 的本地化字符串。
|
||||
/// 查找类似 用户数据已损坏:{0} 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string CoreExceptionServiceUserdataCorruptedMessage {
|
||||
get {
|
||||
@@ -511,7 +511,7 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 无法获取祈愿记录: {0} 的本地化字符串。
|
||||
/// 查找类似 无法获取祈愿记录:{0} 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ServiceGachaLogArchiveCollectionUserdataCorruptedMessage {
|
||||
get {
|
||||
@@ -601,7 +601,7 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 不支持的 Item Id: {0} 的本地化字符串。
|
||||
/// 查找类似 不支持的 Item Id:{0} 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ServiceGachaStatisticsFactoryItemIdInvalid {
|
||||
get {
|
||||
@@ -628,7 +628,7 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 游戏文件操作失败: {0} 的本地化字符串。
|
||||
/// 查找类似 游戏文件操作失败:{0} 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ServiceGameFileOperationExceptionMessage {
|
||||
get {
|
||||
@@ -690,6 +690,15 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 未开启长路径功能,无法设置注册表键值 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ServiceGameRegisteryInteropLongPathsDisabled {
|
||||
get {
|
||||
return ResourceManager.GetString("ServiceGameRegisteryInteropLongPathsDisabled", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 找不到游戏配置文件 {0} 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1537,7 +1546,7 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 清除计划任务失败 的本地化字符串。
|
||||
/// 查找类似 清除计划任务失败,请使用管理员模式重试 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ViewModelExperimentalDeleteTaskWarning {
|
||||
get {
|
||||
@@ -1546,7 +1555,7 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 清除用户数据成功,请立即重启胡桃 的本地化字符串。
|
||||
/// 查找类似 清除用户数据成功,请立即重启胡桃 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ViewModelExperimentalDeleteUserSuccess {
|
||||
get {
|
||||
@@ -1680,6 +1689,15 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 剪贴板中的文本格式不正确 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ViewModelImportFromClipboardErrorTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("ViewModelImportFromClipboardErrorTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 数据格式不正确 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1689,6 +1707,15 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 请先创建一个成就存档 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ViewModelImportWarningMessage2 {
|
||||
get {
|
||||
return ResourceManager.GetString("ViewModelImportWarningMessage2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 导入失败 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2959,7 +2986,7 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 在游戏内切换账号,网络环境发生变化后需要重新手动检测 的本地化字符串。
|
||||
/// 查找类似 在游戏内切换账号,网络环境发生变化后需要重新手动检测 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ViewPageLaunchGameSwitchAccountDescription {
|
||||
get {
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
<value>列表</value>
|
||||
</data>
|
||||
<data name="CoreExceptionServiceUserdataCorruptedMessage" xml:space="preserve">
|
||||
<value>用户数据已损坏: {0}</value>
|
||||
<value>用户数据已损坏:{0}</value>
|
||||
</data>
|
||||
<data name="CoreIOPickerExtensionPickerExceptionInfoBarMessage" xml:space="preserve">
|
||||
<value>请勿在管理员模式下使用此功能 {0}</value>
|
||||
@@ -268,7 +268,7 @@
|
||||
<value>参量质变仪已准备完成</value>
|
||||
</data>
|
||||
<data name="ServiceGachaLogArchiveCollectionUserdataCorruptedMessage" xml:space="preserve">
|
||||
<value>无法获取祈愿记录: {0}</value>
|
||||
<value>无法获取祈愿记录:{0}</value>
|
||||
</data>
|
||||
<data name="ServiceGachaLogEndIdUserdataCorruptedMessage" xml:space="preserve">
|
||||
<value>无法获取祈愿记录 End Id</value>
|
||||
@@ -298,7 +298,7 @@
|
||||
<value>提供的 Url 无效</value>
|
||||
</data>
|
||||
<data name="ServiceGachaStatisticsFactoryItemIdInvalid" xml:space="preserve">
|
||||
<value>不支持的 Item Id: {0}</value>
|
||||
<value>不支持的 Item Id:{0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameDetectGameAccountMultiMatched" xml:space="preserve">
|
||||
<value>存在多个匹配账号,请删除重复的账号</value>
|
||||
@@ -307,7 +307,7 @@
|
||||
<value>查询游戏资源信息</value>
|
||||
</data>
|
||||
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
|
||||
<value>游戏文件操作失败: {0}</value>
|
||||
<value>游戏文件操作失败:{0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLocatorFileOpenPickerCommitText" xml:space="preserve">
|
||||
<value>选择游戏本体</value>
|
||||
@@ -327,6 +327,9 @@
|
||||
<data name="ServiceGamePathLocateFailed" xml:space="preserve">
|
||||
<value>无法找到游戏路径,请前往设置修改</value>
|
||||
</data>
|
||||
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
||||
<value>未开启长路径功能,无法设置注册表键值</value>
|
||||
</data>
|
||||
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
||||
<value>找不到游戏配置文件 {0}</value>
|
||||
</data>
|
||||
@@ -610,10 +613,10 @@
|
||||
<value>清除计划任务成功</value>
|
||||
</data>
|
||||
<data name="ViewModelExperimentalDeleteTaskWarning" xml:space="preserve">
|
||||
<value>清除计划任务失败</value>
|
||||
<value>清除计划任务失败,请使用管理员模式重试</value>
|
||||
</data>
|
||||
<data name="ViewModelExperimentalDeleteUserSuccess" xml:space="preserve">
|
||||
<value>清除用户数据成功,请立即重启胡桃</value>
|
||||
<value>清除用户数据成功,请立即重启胡桃</value>
|
||||
</data>
|
||||
<data name="ViewModelExportSuccessMessage" xml:space="preserve">
|
||||
<value>成功保存到指定位置</value>
|
||||
@@ -657,9 +660,15 @@
|
||||
<data name="ViewModelGachaLogRemoveArchiveTitle" xml:space="preserve">
|
||||
<value>确定要删除存档 {0} 吗?</value>
|
||||
</data>
|
||||
<data name="ViewModelImportFromClipboardErrorTitle" xml:space="preserve">
|
||||
<value>剪贴板中的文本格式不正确</value>
|
||||
</data>
|
||||
<data name="ViewModelImportWarningMessage" xml:space="preserve">
|
||||
<value>数据格式不正确</value>
|
||||
</data>
|
||||
<data name="ViewModelImportWarningMessage2" xml:space="preserve">
|
||||
<value>请先创建一个成就存档</value>
|
||||
</data>
|
||||
<data name="ViewModelImportWarningTitle" xml:space="preserve">
|
||||
<value>导入失败</value>
|
||||
</data>
|
||||
@@ -1084,7 +1093,7 @@
|
||||
<value>绑定当前用户的角色</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSwitchAccountDescription" xml:space="preserve">
|
||||
<value>在游戏内切换账号,网络环境发生变化后需要重新手动检测</value>
|
||||
<value>在游戏内切换账号,网络环境发生变化后需要重新手动检测</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSwitchAccountDetectAction" xml:space="preserve">
|
||||
<value>检测</value>
|
||||
|
||||
@@ -113,7 +113,7 @@ internal class CultivationService : ICultivationService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<BindingInventoryItem> GetInventoryItems(CultivateProject cultivateProject, List<Model.Metadata.Material> metadata)
|
||||
public List<BindingInventoryItem> GetInventoryItems(CultivateProject cultivateProject, List<Model.Metadata.Material> metadata, ICommand saveCommand)
|
||||
{
|
||||
Guid projectId = cultivateProject.InnerId;
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
@@ -127,7 +127,7 @@ internal class CultivationService : ICultivationService
|
||||
foreach (Model.Metadata.Material meta in metadata.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id))
|
||||
{
|
||||
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.Create(projectId, meta.Id);
|
||||
results.Add(new(meta, entity));
|
||||
results.Add(new(meta, entity, saveCommand));
|
||||
}
|
||||
|
||||
return results;
|
||||
@@ -230,7 +230,6 @@ internal class CultivationService : ICultivationService
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
|
||||
return resultItems.OrderByDescending(i => i.Count).ToObservableCollection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,9 @@ internal interface ICultivationService
|
||||
/// </summary>
|
||||
/// <param name="cultivateProject">养成计划</param>
|
||||
/// <param name="metadata">元数据</param>
|
||||
/// <param name="saveCommand">保存命令</param>
|
||||
/// <returns>物品列表</returns>
|
||||
List<Model.Binding.Inventory.InventoryItem> GetInventoryItems(CultivateProject cultivateProject, List<Material> metadata);
|
||||
List<Model.Binding.Inventory.InventoryItem> GetInventoryItems(CultivateProject cultivateProject, List<Material> metadata, ICommand saveCommand);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用于绑定的项目集合
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Service.Game;
|
||||
@@ -34,14 +36,25 @@ 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),
|
||||
CreateNoWindow = true,
|
||||
FileName = "PowerShell",
|
||||
FileName = psExecutablePath,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(startInfo)?.WaitForExit();
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
ThrowHelper.RuntimeEnvironment(SH.ServiceGameRegisteryInteropLongPathsDisabled, ex);
|
||||
}
|
||||
|
||||
if (Get() == account.MihoyoSDK)
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<DebugType>embedded</DebugType>
|
||||
<ApplicationIcon>Assets\Logo.ico</ApplicationIcon>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
|
||||
<AppxBundlePlatforms>x64</AppxBundlePlatforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.ViewModel;
|
||||
using Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI;
|
||||
using System.Runtime.InteropServices;
|
||||
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement;
|
||||
using BindingAchievementGoal = Snap.Hutao.Model.Binding.Achievement.AchievementGoal;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就完成进度更新器
|
||||
/// </summary>
|
||||
internal class AchievementFinishPercentUpdater
|
||||
{
|
||||
private readonly AchievementViewModel viewModel;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的成就进度更新器
|
||||
/// </summary>
|
||||
/// <param name="viewModel">视图模型</param>
|
||||
public AchievementFinishPercentUpdater(AchievementViewModel viewModel)
|
||||
{
|
||||
this.viewModel = viewModel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新完成进度
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
int finished = 0;
|
||||
int count = 0;
|
||||
|
||||
if (viewModel.Achievements is AdvancedCollectionView achievements)
|
||||
{
|
||||
if (viewModel.AchievementGoals is List<BindingAchievementGoal> achievementGoals)
|
||||
{
|
||||
Dictionary<int, AchievementGoalStatistics> counter = achievementGoals.ToDictionary(x => x.Id, x => new AchievementGoalStatistics(x));
|
||||
foreach (BindingAchievement achievement in achievements.SourceCollection.Cast<BindingAchievement>())
|
||||
{
|
||||
// We want to make the state update as fast as possible,
|
||||
// so we use CollectionsMarshal here to get the ref.
|
||||
ref AchievementGoalStatistics stat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
|
||||
|
||||
stat.TotalCount += 1;
|
||||
count += 1;
|
||||
if (achievement.IsChecked)
|
||||
{
|
||||
stat.Finished += 1;
|
||||
finished += 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (AchievementGoalStatistics statistics in counter.Values)
|
||||
{
|
||||
statistics.AchievementGoal.UpdateFinishPercent(statistics.Finished, statistics.TotalCount);
|
||||
}
|
||||
|
||||
viewModel.FinishDescription = $"{finished}/{count} - {(double)finished / count:P2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using BindingAchievementGoal = Snap.Hutao.Model.Binding.Achievement.AchievementGoal;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就分类统计
|
||||
/// </summary>
|
||||
internal struct AchievementGoalStatistics
|
||||
{
|
||||
/// <summary>
|
||||
/// 成就分类
|
||||
/// </summary>
|
||||
public readonly BindingAchievementGoal AchievementGoal;
|
||||
|
||||
/// <summary>
|
||||
/// 完成数
|
||||
/// </summary>
|
||||
public int Finished;
|
||||
|
||||
/// <summary>
|
||||
/// 总数
|
||||
/// </summary>
|
||||
public int TotalCount;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的成就分类统计
|
||||
/// </summary>
|
||||
/// <param name="goal">分类</param>
|
||||
public AchievementGoalStatistics(BindingAchievementGoal goal)
|
||||
{
|
||||
AchievementGoal = goal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Windows.Storage.Pickers;
|
||||
using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就导入器
|
||||
/// </summary>
|
||||
internal class AchievementImporter
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的成就导入器
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public AchievementImporter(IServiceProvider serviceProvider)
|
||||
{
|
||||
achievementService = serviceProvider.GetRequiredService<IAchievementService>();
|
||||
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
|
||||
options = serviceProvider.GetRequiredService<JsonSerializerOptions>();
|
||||
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从剪贴板导入
|
||||
/// </summary>
|
||||
/// <returns>是否导入成功</returns>
|
||||
public async Task<bool> FromClipboardAsync()
|
||||
{
|
||||
if (achievementService.CurrentArchive != null)
|
||||
{
|
||||
if (await GetUIAFFromClipboardAsync().ConfigureAwait(false) is UIAF uiaf)
|
||||
{
|
||||
return await ImportAsync(achievementService.CurrentArchive!, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件导入
|
||||
/// </summary>
|
||||
/// <returns>是否导入成功</returns>
|
||||
public async Task<bool> FromFileAsync()
|
||||
{
|
||||
if (achievementService.CurrentArchive != null)
|
||||
{
|
||||
(bool isPickerOk, FilePath file) = await serviceProvider
|
||||
.GetRequiredService<IPickerFactory>()
|
||||
.GetFileOpenPicker(PickerLocationId.Desktop, SH.FilePickerImportCommit, ".json")
|
||||
.TryPickSingleFileAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (isPickerOk)
|
||||
{
|
||||
(bool isOk, UIAF? uiaf) = await file.DeserializeFromJsonAsync<UIAF>(options).ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
return await ImportAsync(achievementService.CurrentArchive, uiaf!).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<UIAF?> GetUIAFFromClipboardAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Clipboard.DeserializeTextAsync<UIAF>(options).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService?.Error(ex, SH.ViewModelImportFromClipboardErrorTitle);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ImportAsync(EntityAchievementArchive archive, UIAF uiaf)
|
||||
{
|
||||
if (uiaf.IsCurrentVersionSupported())
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
(bool isOk, ImportStrategy strategy) = await new AchievementImportDialog(uiaf).GetImportStrategyAsync().ConfigureAwait(true);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ImportResult result;
|
||||
ContentDialog dialog = await serviceProvider.GetRequiredService<IContentDialogFactory>()
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await using (await dialog.BlockAsync().ConfigureAwait(false))
|
||||
{
|
||||
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
infoBarService.Success(result.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Storage.Pickers;
|
||||
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement;
|
||||
using BindingAchievementGoal = Snap.Hutao.Model.Binding.Achievement.AchievementGoal;
|
||||
@@ -27,7 +26,7 @@ using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
|
||||
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
|
||||
using MetadataAchievementGoal = Snap.Hutao.Model.Metadata.Achievement.AchievementGoal;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就视图模型
|
||||
@@ -45,6 +44,9 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly JsonSerializerOptions options;
|
||||
|
||||
private readonly AchievementFinishPercentUpdater achievementFinishPercentUpdater;
|
||||
private readonly AchievementImporter achievementImporter;
|
||||
|
||||
private readonly TaskCompletionSource<bool> openUICompletionSource = new();
|
||||
|
||||
private AdvancedCollectionView? achievements;
|
||||
@@ -59,6 +61,7 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
/// <summary>
|
||||
/// 构造一个新的成就视图模型
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="metadataService">元数据服务</param>
|
||||
/// <param name="achievementService">成就服务</param>
|
||||
/// <param name="infoBarService">信息条服务</param>
|
||||
@@ -66,6 +69,7 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
/// <param name="contentDialogFactory">内容对话框工厂</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public AchievementViewModel(
|
||||
IServiceProvider serviceProvider,
|
||||
IMetadataService metadataService,
|
||||
IAchievementService achievementService,
|
||||
IInfoBarService infoBarService,
|
||||
@@ -79,13 +83,16 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
this.contentDialogFactory = contentDialogFactory;
|
||||
this.options = options;
|
||||
|
||||
achievementFinishPercentUpdater = new(this);
|
||||
achievementImporter = new(serviceProvider);
|
||||
|
||||
OpenUICommand = new AsyncRelayCommand(OpenUIAsync);
|
||||
ImportUIAFFromClipboardCommand = new AsyncRelayCommand(ImportUIAFFromClipboardAsync);
|
||||
ImportUIAFFromFileCommand = new AsyncRelayCommand(ImportUIAFFromFileAsync);
|
||||
ExportAsUIAFToFileCommand = new AsyncRelayCommand(ExportAsUIAFToFileAsync);
|
||||
AddArchiveCommand = new AsyncRelayCommand(AddArchiveAsync);
|
||||
RemoveArchiveCommand = new AsyncRelayCommand(RemoveArchiveAsync);
|
||||
SearchAchievementCommand = new RelayCommand<string>(SearchAchievement);
|
||||
SearchAchievementCommand = new RelayCommand<string>(UpdateAchievementsFilterBySerach);
|
||||
SortIncompletedSwitchCommand = new RelayCommand(UpdateAchievementsSort);
|
||||
SaveAchievementCommand = new RelayCommand<BindingAchievement>(SaveAchievement);
|
||||
}
|
||||
@@ -146,7 +153,7 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
{
|
||||
SetProperty(ref selectedAchievementGoal, value);
|
||||
SearchText = string.Empty;
|
||||
UpdateAchievementsFilter(value);
|
||||
UpdateAchievementsFilterByGoal(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +284,6 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
openUICompletionSource.TrySetResult(metaInitialized);
|
||||
}
|
||||
|
||||
#region 存档操作
|
||||
private async Task AddArchiveAsync()
|
||||
{
|
||||
if (Archives != null)
|
||||
@@ -324,10 +330,8 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
{
|
||||
try
|
||||
{
|
||||
ThrowIfViewDisposed();
|
||||
using (await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false))
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
ThrowIfViewDisposed();
|
||||
await achievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -341,16 +345,11 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 导入导出
|
||||
private async Task ExportAsUIAFToFileAsync()
|
||||
{
|
||||
if (SelectedArchive == null || Achievements == null)
|
||||
if (SelectedArchive != null && Achievements != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FileSavePicker picker = Ioc.Default.GetRequiredService<IPickerFactory>().GetFileSavePicker();
|
||||
picker.FileTypeChoices.Add(SH.ViewModelAchievementExportFileType, ".json".Enumerate().ToList());
|
||||
picker.SuggestedStartLocation = PickerLocationId.Desktop;
|
||||
@@ -373,102 +372,88 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ImportUIAFFromClipboardAsync()
|
||||
{
|
||||
if (achievementService.CurrentArchive == null)
|
||||
if (await achievementImporter.FromClipboardAsync().ConfigureAwait(false))
|
||||
{
|
||||
// Basically can't happen now
|
||||
// infoBarService.Information("必须选择一个存档才能导入成就");
|
||||
return;
|
||||
}
|
||||
|
||||
if (await GetUIAFFromClipboardAsync().ConfigureAwait(false) is UIAF uiaf)
|
||||
{
|
||||
await TryImportUIAFInternalAsync(achievementService.CurrentArchive!, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
await UpdateAchievementsAsync(achievementService.CurrentArchive!).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ImportUIAFFromFileAsync()
|
||||
{
|
||||
if (achievementService.CurrentArchive == null)
|
||||
if (await achievementImporter.FromFileAsync().ConfigureAwait(false))
|
||||
{
|
||||
// Basically can't happen now
|
||||
// infoBarService.Information("必须选择一个存档才能导入成就");
|
||||
return;
|
||||
}
|
||||
|
||||
FileOpenPicker picker = Ioc.Default.GetRequiredService<IPickerFactory>()
|
||||
.GetFileOpenPicker(PickerLocationId.Desktop, SH.FilePickerImportCommit, ".json");
|
||||
(bool isPickerOk, FilePath file) = await picker.TryPickSingleFileAsync().ConfigureAwait(false);
|
||||
|
||||
if (isPickerOk)
|
||||
{
|
||||
(bool isOk, UIAF? uiaf) = await file.DeserializeFromJsonAsync<UIAF>(options).ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf!).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
}
|
||||
await UpdateAchievementsAsync(achievementService.CurrentArchive!).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<UIAF?> GetUIAFFromClipboardAsync()
|
||||
private async Task UpdateAchievementsAsync(EntityAchievementArchive archive)
|
||||
{
|
||||
List<MetadataAchievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (TryGetAchievements(archive, rawAchievements, out List<BindingAchievement>? combined))
|
||||
{
|
||||
// Assemble achievements on the UI thread.
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
Achievements = new(combined, true);
|
||||
|
||||
UpdateAchievementsFinishPercent();
|
||||
UpdateAchievementsFilterByGoal(SelectedAchievementGoal);
|
||||
UpdateAchievementsSort();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetAchievements(EntityAchievementArchive archive, List<MetadataAchievement> achievements, out List<BindingAchievement>? combined)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Clipboard.DeserializeTextAsync<UIAF>(options).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService?.Error(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> TryImportUIAFInternalAsync(EntityAchievementArchive archive, UIAF uiaf)
|
||||
{
|
||||
if (uiaf.IsCurrentVersionSupported())
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
(bool isOk, ImportStrategy strategy) = await new AchievementImportDialog(uiaf).GetImportStrategyAsync().ConfigureAwait(true);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ImportResult result;
|
||||
ContentDialog dialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await using (await dialog.BlockAsync().ConfigureAwait(false))
|
||||
{
|
||||
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
infoBarService.Success(result.ToString());
|
||||
await UpdateAchievementsAsync(archive).ConfigureAwait(false);
|
||||
combined = achievementService.GetAchievements(archive, achievements);
|
||||
return true;
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
combined = default;
|
||||
infoBarService.Error(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAchievementsSort()
|
||||
{
|
||||
if (Achievements != null)
|
||||
{
|
||||
if (IsIncompletedItemsFirst)
|
||||
{
|
||||
Achievements.SortDescriptions.Add(IncompletedItemsFirstSortDescription);
|
||||
Achievements.SortDescriptions.Add(CompletionTimeSortDescription);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
|
||||
Achievements.SortDescriptions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
private void UpdateAchievementsFilterByGoal(BindingAchievementGoal? goal)
|
||||
{
|
||||
if (Achievements != null)
|
||||
{
|
||||
if (goal == null)
|
||||
{
|
||||
Achievements.Filter = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
int goalId = goal.Id;
|
||||
Achievements.Filter = (object o) => o is BindingAchievement achi && achi.Inner.Goal == goalId;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void SearchAchievement(string? search)
|
||||
private void UpdateAchievementsFilterBySerach(string? search)
|
||||
{
|
||||
if (Achievements != null)
|
||||
{
|
||||
@@ -492,81 +477,10 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateAchievementsAsync(EntityAchievementArchive archive)
|
||||
{
|
||||
List<MetadataAchievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
|
||||
List<BindingAchievement> combined;
|
||||
try
|
||||
{
|
||||
combined = achievementService.GetAchievements(archive, rawAchievements);
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Assemble achievements on the UI thread.
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
Achievements = new(combined, true);
|
||||
|
||||
UpdateAchievementsFinishPercent();
|
||||
UpdateAchievementsFilter(SelectedAchievementGoal);
|
||||
UpdateAchievementsSort();
|
||||
}
|
||||
|
||||
private void UpdateAchievementsSort()
|
||||
{
|
||||
if (Achievements != null)
|
||||
{
|
||||
if (IsIncompletedItemsFirst)
|
||||
{
|
||||
Achievements.SortDescriptions.Add(IncompletedItemsFirstSortDescription);
|
||||
Achievements.SortDescriptions.Add(CompletionTimeSortDescription);
|
||||
}
|
||||
else
|
||||
{
|
||||
Achievements.SortDescriptions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAchievementsFilter(BindingAchievementGoal? goal)
|
||||
{
|
||||
if (Achievements != null)
|
||||
{
|
||||
Achievements.Filter = goal != null
|
||||
? ((object o) => o is BindingAchievement achi && achi.Inner.Goal == goal.Id)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
// 仅 读取成就列表 与 保存成就状态 时需要刷新成就进度
|
||||
private void UpdateAchievementsFinishPercent()
|
||||
{
|
||||
int finished = 0;
|
||||
int count = 0;
|
||||
if (Achievements != null && AchievementGoals != null)
|
||||
{
|
||||
Dictionary<int, GoalStatistics> counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalStatistics(x));
|
||||
foreach (BindingAchievement achievement in Achievements.SourceCollection.Cast<BindingAchievement>())
|
||||
{
|
||||
ref GoalStatistics stat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
|
||||
stat.Count += 1;
|
||||
count += 1;
|
||||
if (achievement.IsChecked)
|
||||
{
|
||||
stat.Finished += 1;
|
||||
finished += 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (GoalStatistics statistics in counter.Values)
|
||||
{
|
||||
statistics.AchievementGoal.UpdateFinishPercent(statistics.Finished, statistics.Count);
|
||||
}
|
||||
|
||||
FinishDescription = $"{finished}/{count} - {(double)finished / count:P2}";
|
||||
}
|
||||
achievementFinishPercentUpdater.Update();
|
||||
}
|
||||
|
||||
private void SaveAchievement(BindingAchievement? achievement)
|
||||
@@ -577,16 +491,4 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
UpdateAchievementsFinishPercent();
|
||||
}
|
||||
}
|
||||
|
||||
private struct GoalStatistics
|
||||
{
|
||||
public readonly BindingAchievementGoal AchievementGoal;
|
||||
public int Finished;
|
||||
public int Count;
|
||||
|
||||
public GoalStatistics(BindingAchievementGoal goal)
|
||||
{
|
||||
AchievementGoal = goal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,7 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel
|
||||
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// take a short path if avatar is not saved.
|
||||
// take a hot path if avatar is not saved.
|
||||
bool avatarAndWeaponSaved = avatarSaved && await cultivationService
|
||||
.SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull())
|
||||
.ConfigureAwait(false);
|
||||
@@ -258,11 +258,8 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel
|
||||
|
||||
private async Task ExportAsImageAsync(UIElement? element)
|
||||
{
|
||||
if (element == null)
|
||||
if (element != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RenderTargetBitmap bitmap = new();
|
||||
await bitmap.RenderAsync(element);
|
||||
|
||||
@@ -273,7 +270,6 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel
|
||||
Color tintColor = (Color)Ioc.Default.GetRequiredService<App>().Resources["CompatBackgroundColor"];
|
||||
Bgra8 tint = Bgra8.FromColor(tintColor);
|
||||
softwareBitmap.NormalBlend(tint);
|
||||
|
||||
using (InMemoryRandomAccessStream memory = new())
|
||||
{
|
||||
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, memory);
|
||||
@@ -302,3 +298,4 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ internal class CultivationViewModel : Abstraction.ViewModel
|
||||
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
CultivateEntries = entries;
|
||||
InventoryItems = cultivationService.GetInventoryItems(project, materials);
|
||||
InventoryItems = cultivationService.GetInventoryItems(project, materials, SaveInventoryItemCommand);
|
||||
|
||||
await UpdateStatisticsItemsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Game.Locator;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Pickers;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
Reference in New Issue
Block a user