mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor achievement viewmodel
This commit is contained in:
@@ -40,15 +40,23 @@ internal sealed class HutaoException : Exception
|
||||
}
|
||||
}
|
||||
|
||||
public static HutaoException ServiceTypeCastFailed<TFrom, TTo>(string name, Exception? innerException = default)
|
||||
{
|
||||
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
|
||||
throw new HutaoException(HutaoExceptionKind.ServiceTypeCastFailed, message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static HutaoException GachaStatisticsInvalidItemId(uint id, Exception? innerException = default)
|
||||
{
|
||||
string message = SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id);
|
||||
throw new HutaoException(HutaoExceptionKind.GachaStatisticsInvalidItemId, message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static InvalidCastException InvalidCast<TFrom, TTo>(string name, Exception? innerException = default)
|
||||
{
|
||||
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
|
||||
throw new InvalidCastException(message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? innerException = default)
|
||||
{
|
||||
return new OperationCanceledException(message, innerException);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ internal enum HutaoExceptionKind
|
||||
None,
|
||||
|
||||
// Foundation
|
||||
ServiceTypeCastFailed,
|
||||
ImageCacheInvalidUri,
|
||||
|
||||
// IO
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Snap.Hutao.ViewModel.Abstraction;
|
||||
/// 视图模型抽象类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
{
|
||||
private bool isInitialized;
|
||||
@@ -28,9 +29,15 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
[Command("OpenUICommand")]
|
||||
protected virtual async Task OpenUIAsync()
|
||||
{
|
||||
// Set value on UI thread
|
||||
IsInitialized = await InitializeUIAsync().ConfigureAwait(true);
|
||||
Initialization.TrySetResult(IsInitialized);
|
||||
try
|
||||
{
|
||||
// ConfigureAwait(true) sets value on UI thread
|
||||
IsInitialized = await InitializeUIAsync().ConfigureAwait(true);
|
||||
Initialization.TrySetResult(IsInitialized);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual ValueTask<bool> InitializeUIAsync()
|
||||
@@ -46,6 +53,8 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
return disposable;
|
||||
}
|
||||
|
||||
#region SetProperty
|
||||
|
||||
protected new bool SetProperty<T>([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
return !IsViewDisposed && base.SetProperty(ref field, newValue, propertyName);
|
||||
@@ -99,12 +108,13 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void ThrowIfViewDisposed()
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
{
|
||||
ThrowHelper.OperationCanceled(SH.ViewModelViewDisposedOperationCancel);
|
||||
HutaoException.OperationCanceled(SH.ViewModelViewDisposedOperationCancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -33,19 +34,18 @@ internal static class AchievementFinishPercent
|
||||
|
||||
if (achievements.SourceCollection is not List<AchievementView> list)
|
||||
{
|
||||
// Fast path
|
||||
throw Must.NeverHappen("AchievementViewModel.Achievements.SourceCollection 应为 List<AchievementView>");
|
||||
throw HutaoException.InvalidCast<IEnumerable<AchievementView>, List<AchievementView>>("AchievementViewModel.Achievements.SourceCollection");
|
||||
}
|
||||
|
||||
Dictionary<AchievementGoalId, AchievementGoalStatistics> counter = achievementGoals.ToDictionary(x => x.Id, AchievementGoalStatistics.From);
|
||||
|
||||
foreach (ref readonly AchievementView achievement in CollectionsMarshal.AsSpan(list))
|
||||
foreach (ref readonly AchievementView achievementView in CollectionsMarshal.AsSpan(list))
|
||||
{
|
||||
ref AchievementGoalStatistics goalStat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
|
||||
ref AchievementGoalStatistics goalStat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievementView.Inner.Goal);
|
||||
|
||||
goalStat.TotalCount += 1;
|
||||
totalCount += 1;
|
||||
if (achievement.IsChecked)
|
||||
if (achievementView.IsChecked)
|
||||
{
|
||||
goalStat.Finished += 1;
|
||||
totalFinished += 1;
|
||||
|
||||
@@ -15,123 +15,99 @@ using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就导入器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementImporter
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IClipboardProvider clipboardInterop;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly AchievementImporterDependencies dependencies;
|
||||
|
||||
/// <summary>
|
||||
/// 从剪贴板导入
|
||||
/// </summary>
|
||||
/// <returns>是否导入成功</returns>
|
||||
public async ValueTask<bool> FromClipboardAsync()
|
||||
{
|
||||
if (achievementService.CurrentArchive is { } archive)
|
||||
if (dependencies.AchievementService.CurrentArchive is not { } archive)
|
||||
{
|
||||
if (await TryCatchGetUIAFFromClipboardAsync().ConfigureAwait(false) is { } uiaf)
|
||||
{
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (await TryCatchGetUIAFFromClipboardAsync().ConfigureAwait(false) is not { } uiaf)
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件导入
|
||||
/// </summary>
|
||||
/// <returns>是否导入成功</returns>
|
||||
public async ValueTask<bool> FromFileAsync()
|
||||
{
|
||||
if (achievementService.CurrentArchive is { } archive)
|
||||
if (dependencies.AchievementService.CurrentArchive is not { } archive)
|
||||
{
|
||||
ValueResult<bool, ValueFile> pickerResult = fileSystemPickerInteraction.PickFile(
|
||||
SH.ServiceAchievementUIAFImportPickerTitile,
|
||||
[(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]);
|
||||
|
||||
if (pickerResult.TryGetValue(out ValueFile file))
|
||||
{
|
||||
ValueResult<bool, UIAF?> uiafResult = await file.DeserializeFromJsonAsync<UIAF>(options).ConfigureAwait(false);
|
||||
|
||||
if (uiafResult.TryGetValue(out UIAF? uiaf))
|
||||
{
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
ValueResult<bool, ValueFile> pickerResult = dependencies.FileSystemPickerInteraction.PickFile(
|
||||
SH.ServiceAchievementUIAFImportPickerTitile,
|
||||
[(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]);
|
||||
|
||||
if (!pickerResult.TryGetValue(out ValueFile file))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ValueResult<bool, UIAF?> uiafResult = await file.DeserializeFromJsonAsync<UIAF>(dependencies.JsonSerializerOptions).ConfigureAwait(false);
|
||||
|
||||
if (!uiafResult.TryGetValue(out UIAF? uiaf))
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask<UIAF?> TryCatchGetUIAFFromClipboardAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await clipboardInterop.DeserializeFromJsonAsync<UIAF>().ConfigureAwait(false);
|
||||
return await dependencies.ClipboardProvider.DeserializeFromJsonAsync<UIAF>().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService?.Error(ex, SH.ViewModelImportFromClipboardErrorTitle);
|
||||
dependencies.InfoBarService.Error(ex, SH.ViewModelImportFromClipboardErrorTitle);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<bool> TryImportAsync(EntityAchievementArchive archive, UIAF uiaf)
|
||||
{
|
||||
if (uiaf.IsCurrentVersionSupported())
|
||||
if (!uiaf.IsCurrentVersionSupported())
|
||||
{
|
||||
AchievementImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync<AchievementImportDialog>(uiaf).ConfigureAwait(false);
|
||||
(bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ImportResult result;
|
||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
infoBarService.Success($"{result}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
AchievementImportDialog importDialog = await dependencies.ContentDialogFactory
|
||||
.CreateInstanceAsync<AchievementImportDialog>(uiaf).ConfigureAwait(false);
|
||||
(bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = await dependencies.ContentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ImportResult result;
|
||||
using (await dialog.BlockAsync(dependencies.TaskContext).ConfigureAwait(false))
|
||||
{
|
||||
result = await dependencies.AchievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
dependencies.InfoBarService.Success($"{result}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementImporterDependencies
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IClipboardProvider clipboardProvider;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public IFileSystemPickerInteraction FileSystemPickerInteraction { get => fileSystemPickerInteraction; }
|
||||
|
||||
public JsonSerializerOptions JsonSerializerOptions { get => jsonSerializerOptions; }
|
||||
|
||||
public IContentDialogFactory ContentDialogFactory { get => contentDialogFactory; }
|
||||
|
||||
public IAchievementService AchievementService { get => achievementService; }
|
||||
|
||||
public IClipboardProvider ClipboardProvider { get => clipboardProvider; }
|
||||
|
||||
public IInfoBarService InfoBarService { get => infoBarService; }
|
||||
|
||||
public ITaskContext TaskContext { get => taskContext; }
|
||||
}
|
||||
@@ -5,8 +5,6 @@ using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
@@ -23,9 +21,6 @@ using SortDirection = CommunityToolkit.WinUI.Collections.SortDirection;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就视图模型
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
@@ -34,14 +29,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
private readonly SortDescription uncompletedItemsFirstSortDescription = new(nameof(AchievementView.IsChecked), SortDirection.Ascending);
|
||||
private readonly SortDescription completionTimeSortDescription = new(nameof(AchievementView.Time), SortDirection.Descending);
|
||||
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly AchievementImporter achievementImporter;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly AchievementViewModelDependencies dependencies;
|
||||
|
||||
private AdvancedCollectionView<AchievementView>? achievements;
|
||||
private List<AchievementGoalView>? achievementGoals;
|
||||
@@ -52,18 +40,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
private string searchText = string.Empty;
|
||||
private string? finishDescription;
|
||||
|
||||
/// <summary>
|
||||
/// 成就存档集合
|
||||
/// </summary>
|
||||
public ObservableCollection<EntityAchievementArchive>? Archives
|
||||
{
|
||||
get => archives;
|
||||
set => SetProperty(ref archives, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选中的成就存档
|
||||
/// </summary>
|
||||
public EntityAchievementArchive? SelectedArchive
|
||||
{
|
||||
get => selectedArchive;
|
||||
@@ -71,38 +53,24 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
if (SetProperty(ref selectedArchive, value))
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
achievementService.CurrentArchive = value;
|
||||
dependencies.AchievementService.CurrentArchive = value;
|
||||
UpdateAchievementsAsync(value).SafeForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成就视图
|
||||
/// </summary>
|
||||
public AdvancedCollectionView<AchievementView>? Achievements
|
||||
{
|
||||
get => achievements;
|
||||
set => SetProperty(ref achievements, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成就分类
|
||||
/// </summary>
|
||||
public List<AchievementGoalView>? AchievementGoals
|
||||
{
|
||||
get => achievementGoals;
|
||||
set => SetProperty(ref achievementGoals, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选中的成就分类
|
||||
/// </summary>
|
||||
public AchievementGoalView? SelectedAchievementGoal
|
||||
{
|
||||
get => selectedAchievementGoal;
|
||||
@@ -116,27 +84,18 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索文本
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
{
|
||||
get => searchText;
|
||||
set => SetProperty(ref searchText, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 未完成优先
|
||||
/// </summary>
|
||||
public bool IsUncompletedItemsFirst
|
||||
{
|
||||
get => isUncompletedItemsFirst;
|
||||
set => SetProperty(ref isUncompletedItemsFirst, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完成进度描述
|
||||
/// </summary>
|
||||
public string? FinishDescription
|
||||
{
|
||||
get => finishDescription;
|
||||
@@ -160,36 +119,30 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
if (!await dependencies.MetadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
List<AchievementGoalView> sortedGoals;
|
||||
ObservableCollection<EntityAchievementArchive> archives;
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
List<MetadataAchievementGoal> goals = await metadataService
|
||||
.GetAchievementGoalListAsync(CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
|
||||
archives = achievementService.ArchiveCollection;
|
||||
}
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
AchievementGoals = sortedGoals;
|
||||
Archives = archives;
|
||||
SelectedArchive = achievementService.CurrentArchive;
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
List<AchievementGoalView> sortedGoals;
|
||||
ObservableCollection<EntityAchievementArchive> archives;
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
List<MetadataAchievementGoal> goals = await dependencies.MetadataService
|
||||
.GetAchievementGoalListAsync(CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
|
||||
archives = dependencies.AchievementService.ArchiveCollection;
|
||||
}
|
||||
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
|
||||
AchievementGoals = sortedGoals;
|
||||
Archives = archives;
|
||||
SelectedArchive = dependencies.AchievementService.CurrentArchive;
|
||||
return true;
|
||||
}
|
||||
|
||||
[GeneratedRegex("\\d\\.\\d")]
|
||||
@@ -200,25 +153,25 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
if (Archives is not null)
|
||||
{
|
||||
AchievementArchiveCreateDialog dialog = await contentDialogFactory.CreateInstanceAsync<AchievementArchiveCreateDialog>().ConfigureAwait(false);
|
||||
AchievementArchiveCreateDialog dialog = await dependencies.ContentDialogFactory.CreateInstanceAsync<AchievementArchiveCreateDialog>().ConfigureAwait(false);
|
||||
(bool isOk, string name) = await dialog.GetInputAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ArchiveAddResult result = await achievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false);
|
||||
ArchiveAddResult result = await dependencies.AchievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case ArchiveAddResult.Added:
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
SelectedArchive = achievementService.CurrentArchive;
|
||||
infoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name));
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
SelectedArchive = dependencies.AchievementService.CurrentArchive;
|
||||
dependencies.InfoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name));
|
||||
break;
|
||||
case ArchiveAddResult.InvalidName:
|
||||
infoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName);
|
||||
break;
|
||||
case ArchiveAddResult.AlreadyExists:
|
||||
infoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name));
|
||||
dependencies.InfoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name));
|
||||
break;
|
||||
default:
|
||||
throw Must.NeverHappen();
|
||||
@@ -234,7 +187,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
string title = SH.FormatViewModelAchievementRemoveArchiveTitle(SelectedArchive.Name);
|
||||
string content = SH.ViewModelAchievementRemoveArchiveContent;
|
||||
ContentDialogResult result = await contentDialogFactory
|
||||
ContentDialogResult result = await dependencies.ContentDialogFactory
|
||||
.CreateForConfirmCancelAsync(title, content)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -244,11 +197,11 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
await achievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
|
||||
await dependencies.AchievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Re-select first archive
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
SelectedArchive = Archives.FirstOrDefault();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -263,21 +216,21 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
if (SelectedArchive is not null && Achievements is not null)
|
||||
{
|
||||
(bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile(
|
||||
(bool isOk, ValueFile file) = dependencies.FileSystemPickerInteraction.SaveFile(
|
||||
SH.ViewModelAchievementUIAFExportPickerTitle,
|
||||
$"{achievementService.CurrentArchive?.Name}.json",
|
||||
$"{dependencies.AchievementService.CurrentArchive?.Name}.json",
|
||||
[(SH.ViewModelAchievementExportFileType, "*.json")]);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
UIAF uiaf = await achievementService.ExportToUIAFAsync(SelectedArchive).ConfigureAwait(false);
|
||||
if (await file.SerializeToJsonAsync(uiaf, options).ConfigureAwait(false))
|
||||
UIAF uiaf = await dependencies.AchievementService.ExportToUIAFAsync(SelectedArchive).ConfigureAwait(false);
|
||||
if (await file.SerializeToJsonAsync(uiaf, dependencies.JsonSerializerOptions).ConfigureAwait(false))
|
||||
{
|
||||
infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
|
||||
dependencies.InfoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,20 +239,20 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
[Command("ImportUIAFFromClipboardCommand")]
|
||||
private async Task ImportUIAFFromClipboardAsync()
|
||||
{
|
||||
if (await achievementImporter.FromClipboardAsync().ConfigureAwait(false))
|
||||
if (await dependencies.AchievementImporter.FromClipboardAsync().ConfigureAwait(false))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(achievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(achievementService.CurrentArchive).ConfigureAwait(false);
|
||||
ArgumentNullException.ThrowIfNull(dependencies.AchievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(dependencies.AchievementService.CurrentArchive).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ImportUIAFFromFileCommand")]
|
||||
private async Task ImportUIAFFromFileAsync()
|
||||
{
|
||||
if (await achievementImporter.FromFileAsync().ConfigureAwait(false))
|
||||
if (await dependencies.AchievementImporter.FromFileAsync().ConfigureAwait(false))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(achievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(achievementService.CurrentArchive).ConfigureAwait(false);
|
||||
ArgumentNullException.ThrowIfNull(dependencies.AchievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(dependencies.AchievementService.CurrentArchive).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,11 +264,11 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
return;
|
||||
}
|
||||
|
||||
List<MetadataAchievement> achievements = await metadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false);
|
||||
List<MetadataAchievement> achievements = await dependencies.MetadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined))
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
|
||||
Achievements = new(combined, true);
|
||||
UpdateAchievementsFinishPercent();
|
||||
@@ -328,12 +281,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
try
|
||||
{
|
||||
combined = achievementService.GetAchievementViewList(archive, achievements);
|
||||
combined = dependencies.AchievementService.GetAchievementViewList(archive, achievements);
|
||||
return true;
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
dependencies.InfoBarService.Error(ex);
|
||||
combined = default;
|
||||
return false;
|
||||
}
|
||||
@@ -418,7 +371,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
if (achievement is not null)
|
||||
{
|
||||
achievementService.SaveAchievement(achievement);
|
||||
dependencies.AchievementService.SaveAchievement(achievement);
|
||||
UpdateAchievementsFinishPercent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementViewModelDependencies
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly AchievementImporter achievementImporter;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public IFileSystemPickerInteraction FileSystemPickerInteraction { get => fileSystemPickerInteraction; }
|
||||
|
||||
public JsonSerializerOptions JsonSerializerOptions { get => jsonSerializerOptions; }
|
||||
|
||||
public IContentDialogFactory ContentDialogFactory { get => contentDialogFactory; }
|
||||
|
||||
public AchievementImporter AchievementImporter { get => achievementImporter; }
|
||||
|
||||
public IAchievementService AchievementService { get => achievementService; }
|
||||
|
||||
public IMetadataService MetadataService { get => metadataService; }
|
||||
|
||||
public IInfoBarService InfoBarService { get => infoBarService; }
|
||||
|
||||
public ITaskContext TaskContext { get => taskContext; }
|
||||
}
|
||||
@@ -121,7 +121,7 @@ internal sealed class DownloadSummary : ObservableObject
|
||||
{
|
||||
if (imageCache is not IImageCacheFilePathOperation imageCacheFilePathOperation)
|
||||
{
|
||||
throw HutaoException.ServiceTypeCastFailed<IImageCache, IImageCacheFilePathOperation>(nameof(imageCache));
|
||||
throw HutaoException.InvalidCast<IImageCache, IImageCacheFilePathOperation>(nameof(imageCache));
|
||||
}
|
||||
|
||||
using (ZipArchive archive = new(stream))
|
||||
|
||||
Reference in New Issue
Block a user