fix translation

This commit is contained in:
DismissedLight
2023-02-09 12:26:42 +08:00
parent 6c83cd3da5
commit 165c33ef2c
26 changed files with 516 additions and 259 deletions

View File

@@ -14,7 +14,7 @@ body:
id: back
attributes:
label: 背景与动机
description: 添加此功能的理由
description: 添加此功能的理由,如果你想要实现多个功能,请分别发起多个单独的 Issue
validations:
required: true

View File

@@ -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)

View File

@@ -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)

View File

@@ -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));

View File

@@ -40,7 +40,5 @@ internal class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
{
Command?.Execute(CommandParameter);
}
base.OnAssociatedObjectLoaded();
}
}

View File

@@ -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)
{
}
}

View File

@@ -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);
}
}

View File

@@ -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>()

View File

@@ -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);
}
}
}

View File

@@ -50,7 +50,7 @@ public class UIGF
{
foreach (UIGFItem item in List)
{
if (item.ItemType != "角色" || item.ItemType != "武器")
if (item.ItemType != "角色" && item.ItemType != "武器")
{
return false;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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>
/// 获取用于绑定的项目集合

View File

@@ -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;

View File

@@ -34,7 +34,7 @@
<DebugType>embedded</DebugType>
<ApplicationIcon>Assets\Logo.ico</ApplicationIcon>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
<AppxBundlePlatforms>x64</AppxBundlePlatforms>
</PropertyGroup>
<ItemGroup>

View File

@@ -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;

View File

@@ -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}";
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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);
}

View File

@@ -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;