diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs index 09212166..b204de5f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs @@ -40,15 +40,23 @@ internal sealed class HutaoException : Exception } } - public static HutaoException ServiceTypeCastFailed(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(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); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoExceptionKind.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoExceptionKind.cs index 80333902..b55e7c58 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoExceptionKind.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoExceptionKind.cs @@ -8,7 +8,6 @@ internal enum HutaoExceptionKind None, // Foundation - ServiceTypeCastFailed, ImageCacheInvalidUri, // IO diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs index e256022a..30771548 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs @@ -11,6 +11,7 @@ namespace Snap.Hutao.ViewModel.Abstraction; /// 视图模型抽象类 /// [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 InitializeUIAsync() @@ -46,6 +53,8 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel return disposable; } + #region SetProperty + protected new bool SetProperty([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); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementFinishPercent.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementFinishPercent.cs index 7a4678cd..4638ba82 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementFinishPercent.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementFinishPercent.cs @@ -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 list) { - // Fast path - throw Must.NeverHappen("AchievementViewModel.Achievements.SourceCollection 应为 List"); + throw HutaoException.InvalidCast, List>("AchievementViewModel.Achievements.SourceCollection"); } Dictionary 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; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs index b26b747a..34cae197 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporter.cs @@ -15,123 +15,99 @@ using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive; namespace Snap.Hutao.ViewModel.Achievement; -/// -/// 成就导入器 -/// -[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; - /// - /// 从剪贴板导入 - /// - /// 是否导入成功 public async ValueTask 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); } - /// - /// 从文件导入 - /// - /// 是否导入成功 public async ValueTask FromFileAsync() { - if (achievementService.CurrentArchive is { } archive) + if (dependencies.AchievementService.CurrentArchive is not { } archive) { - ValueResult pickerResult = fileSystemPickerInteraction.PickFile( - SH.ServiceAchievementUIAFImportPickerTitile, - [(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]); - - if (pickerResult.TryGetValue(out ValueFile file)) - { - ValueResult uiafResult = await file.DeserializeFromJsonAsync(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 pickerResult = dependencies.FileSystemPickerInteraction.PickFile( + SH.ServiceAchievementUIAFImportPickerTitile, + [(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]); + + if (!pickerResult.TryGetValue(out ValueFile file)) + { + return false; + } + + ValueResult uiafResult = await file.DeserializeFromJsonAsync(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 TryCatchGetUIAFFromClipboardAsync() { try { - return await clipboardInterop.DeserializeFromJsonAsync().ConfigureAwait(false); + return await dependencies.ClipboardProvider.DeserializeFromJsonAsync().ConfigureAwait(false); } catch (Exception ex) { - infoBarService?.Error(ex, SH.ViewModelImportFromClipboardErrorTitle); + dependencies.InfoBarService.Error(ex, SH.ViewModelImportFromClipboardErrorTitle); return null; } } private async ValueTask TryImportAsync(EntityAchievementArchive archive, UIAF uiaf) { - if (uiaf.IsCurrentVersionSupported()) + if (!uiaf.IsCurrentVersionSupported()) { - AchievementImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync(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(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; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporterDependencies.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporterDependencies.cs new file mode 100644 index 00000000..0d7d9e82 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementImporterDependencies.cs @@ -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; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs index d50110b8..768a9a10 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs @@ -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; -/// -/// 成就视图模型 -/// [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? achievements; private List? achievementGoals; @@ -52,18 +40,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav private string searchText = string.Empty; private string? finishDescription; - /// - /// 成就存档集合 - /// public ObservableCollection? Archives { get => archives; set => SetProperty(ref archives, value); } - /// - /// 选中的成就存档 - /// 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(); } } } - /// - /// 成就视图 - /// public AdvancedCollectionView? Achievements { get => achievements; set => SetProperty(ref achievements, value); } - /// - /// 成就分类 - /// public List? AchievementGoals { get => achievementGoals; set => SetProperty(ref achievementGoals, value); } - /// - /// 选中的成就分类 - /// public AchievementGoalView? SelectedAchievementGoal { get => selectedAchievementGoal; @@ -116,27 +84,18 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav } } - /// - /// 搜索文本 - /// public string SearchText { get => searchText; set => SetProperty(ref searchText, value); } - /// - /// 未完成优先 - /// public bool IsUncompletedItemsFirst { get => isUncompletedItemsFirst; set => SetProperty(ref isUncompletedItemsFirst, value); } - /// - /// 完成进度描述 - /// public string? FinishDescription { get => finishDescription; @@ -160,36 +119,30 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav protected override async ValueTask InitializeUIAsync() { - if (await metadataService.InitializeAsync().ConfigureAwait(false)) + if (!await dependencies.MetadataService.InitializeAsync().ConfigureAwait(false)) { - try - { - List sortedGoals; - ObservableCollection archives; - - using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) - { - List 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 sortedGoals; + ObservableCollection archives; + + using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) + { + List 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().ConfigureAwait(false); + AchievementArchiveCreateDialog dialog = await dependencies.ContentDialogFactory.CreateInstanceAsync().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 achievements = await metadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false); + List achievements = await dependencies.MetadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false); if (TryGetAchievements(archive, achievements, out List? 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(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModelDependencies.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModelDependencies.cs new file mode 100644 index 00000000..211e61c2 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModelDependencies.cs @@ -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; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/DownloadSummary.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/DownloadSummary.cs index bd98fd8c..628c1a7f 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/DownloadSummary.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/DownloadSummary.cs @@ -121,7 +121,7 @@ internal sealed class DownloadSummary : ObservableObject { if (imageCache is not IImageCacheFilePathOperation imageCacheFilePathOperation) { - throw HutaoException.ServiceTypeCastFailed(nameof(imageCache)); + throw HutaoException.InvalidCast(nameof(imageCache)); } using (ZipArchive archive = new(stream))