diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 74061358..301bfb91 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -1247,6 +1247,18 @@ 添加或更新到当前养成计划 + + 总是创建新的养成目标物品 + + + 保存方式 + + + 覆盖存在的养成目标物品 + + + 保留存在的养成目标物品 + 每日委托上线提醒 @@ -1598,6 +1610,15 @@ 操作未全部完成:添加 / 更新:{0} 个,跳过 {1} 个 + + 选定的等级不需要养成材料 + + + 尚未创建并选择养成计划 + + + 已存在该物品的养成项目 + 已成功添加至当前养成计划 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs index 2ccf0181..49e22838 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs @@ -8,12 +8,10 @@ using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.InterChange.Achievement; using Snap.Hutao.Model.Primitive; using Snap.Hutao.ViewModel.Achievement; -using System.Collections.ObjectModel; using EntityAchievement = Snap.Hutao.Model.Entity.Achievement; namespace Snap.Hutao.Service.Achievement; -[HighQuality] [ConstructorGenerated] [Injection(InjectAs.Scoped, typeof(IAchievementService))] internal sealed partial class AchievementService : IAchievementService diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs index 02aa3da0..7141a443 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs @@ -4,6 +4,7 @@ using Snap.Hutao.Model.Metadata; using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Metadata.ContextAbstraction; +using Snap.Hutao.UI.Xaml.Data; using Snap.Hutao.ViewModel.AvatarProperty; namespace Snap.Hutao.Service.AvatarInfo.Factory; @@ -38,7 +39,7 @@ internal sealed partial class SummaryFactory : ISummaryFactory return new() { - Avatars = new(views), + Avatars = views.ToAdvancedCollectionView(), }; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ConsumptionSaveResultKind.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ConsumptionSaveResultKind.cs new file mode 100644 index 00000000..3244fa10 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ConsumptionSaveResultKind.cs @@ -0,0 +1,12 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Cultivation; + +internal enum ConsumptionSaveResultKind +{ + NoItem, + NoProject, + Skipped, + Added, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ConsumptionSaveStrategyKind.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ConsumptionSaveStrategyKind.cs new file mode 100644 index 00000000..4f5de156 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ConsumptionSaveStrategyKind.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Cultivation; + +internal enum ConsumptionSaveStrategyKind +{ + PreserveExisting, + OverwriteExisting, + CreateNewEntry, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs index 10951cf2..f7587642 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -8,7 +8,6 @@ using Snap.Hutao.Service.Inventory; using Snap.Hutao.Service.Metadata.ContextAbstraction; using Snap.Hutao.ViewModel.Cultivation; using System.Collections.ObjectModel; -using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item; using ModelItem = Snap.Hutao.Model.Item; namespace Snap.Hutao.Service.Cultivation; @@ -115,45 +114,54 @@ internal sealed partial class CultivationService : ICultivationService } /// - public async ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items, LevelInformation levelInformation) + public async ValueTask SaveConsumptionAsync(InputConsumption inputConsumption) { - if (items.Count == 0) + if (inputConsumption.Items.Count == 0) { - return true; + return ConsumptionSaveResultKind.NoItem; } - await taskContext.SwitchToMainThreadAsync(); if (Projects.CurrentItem is null) { + await taskContext.SwitchToMainThreadAsync(); Projects.MoveCurrentTo(Projects.SourceCollection.SelectedOrDefault()); if (Projects.CurrentItem is null) { - return false; + return ConsumptionSaveResultKind.NoProject; } } await taskContext.SwitchToBackgroundAsync(); - CultivateEntry? entry = type is CultivateType.AvatarAndSkill - ? cultivationDbService.GetCultivateEntryByProjectIdAndItemId(Projects.CurrentItem.InnerId, itemId) - : default; + + CultivateEntry? entry = default; + + if (inputConsumption.Strategy is ConsumptionSaveStrategyKind.PreserveExisting or ConsumptionSaveStrategyKind.OverwriteExisting) + { + entry = cultivationDbService.GetCultivateEntryByProjectIdAndItemId(Projects.CurrentItem.InnerId, inputConsumption.ItemId); + + if (inputConsumption.Strategy is ConsumptionSaveStrategyKind.PreserveExisting && entry is not null) + { + return ConsumptionSaveResultKind.Skipped; + } + } if (entry is null) { - entry = CultivateEntry.From(Projects.CurrentItem.InnerId, type, itemId); + entry = CultivateEntry.From(Projects.CurrentItem.InnerId, inputConsumption.Type, inputConsumption.ItemId); cultivationDbService.AddCultivateEntry(entry); } Guid entryId = entry.InnerId; cultivationDbService.RemoveLevelInformationByEntryId(entryId); - CultivateEntryLevelInformation entryLevelInformation = CultivateEntryLevelInformation.From(entryId, type, levelInformation); + CultivateEntryLevelInformation entryLevelInformation = CultivateEntryLevelInformation.From(entryId, inputConsumption.Type, inputConsumption.LevelInformation); cultivationDbService.AddLevelInformation(entryLevelInformation); cultivationDbService.RemoveCultivateItemRangeByEntryId(entryId); - IEnumerable toAdd = items.Select(item => CultivateItem.From(entryId, item)); + IEnumerable toAdd = inputConsumption.Items.Select(item => CultivateItem.From(entryId, item)); cultivationDbService.AddCultivateItemRange(toAdd); - return true; + return ConsumptionSaveResultKind.Added; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs index 06e2a724..64bc7eca 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs @@ -3,22 +3,13 @@ using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; -using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.ViewModel.Cultivation; -using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using System.Collections.ObjectModel; namespace Snap.Hutao.Service.Cultivation; -/// -/// 养成计算服务 -/// -[HighQuality] internal interface ICultivationService { - /// - /// 获取用于绑定的项目集合 - /// AdvancedDbCollectionView Projects { get; } ValueTask> GetCultivateEntriesAsync(CultivateProject cultivateProject, ICultivationMetadataContext context); @@ -26,32 +17,13 @@ internal interface ICultivationService ValueTask> GetStatisticsCultivateItemCollectionAsync( CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token); - /// - /// 删除养成清单 - /// - /// 入口Id - /// 任务 ValueTask RemoveCultivateEntryAsync(Guid entryId); - /// - /// 异步移除项目 - /// - /// 项目 - /// 任务 ValueTask RemoveProjectAsync(CultivateProject project); - ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items, LevelInformation levelInformation); + ValueTask SaveConsumptionAsync(InputConsumption inputConsumption); - /// - /// 保存养成物品状态 - /// - /// 养成物品 void SaveCultivateItem(CultivateItemView item); - /// - /// 异步尝试添加新的项目 - /// - /// 项目 - /// 添加操作的结果 ValueTask TryAddProjectAsync(CultivateProject project); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/InputConsumption.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/InputConsumption.cs new file mode 100644 index 00000000..605c5155 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/InputConsumption.cs @@ -0,0 +1,20 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Entity.Primitive; +using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item; + +namespace Snap.Hutao.Service.Cultivation; + +internal sealed class InputConsumption +{ + public required CultivateType Type { get; init; } + + public required uint ItemId { get; init; } + + public required List Items { get; init; } + + public required LevelInformation LevelInformation { get; init; } + + public required ConsumptionSaveStrategyKind Strategy { get; init; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs index a25c75bf..65b201f7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs @@ -203,7 +203,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory AsyncBarrier barrier = new(4); List historyWishes = historyWishBuilders - .Where(b => appOptions.IsEmptyHistoryWishVisible || (!b.IsEmpty)) + .Where(b => appOptions.IsEmptyHistoryWishVisible || !b.IsEmpty) .OrderByDescending(builder => builder.From) .ThenBy(builder => builder.ConfigType, GachaTypeComparer.Shared) .Select(builder => builder.ToHistoryWish()) @@ -212,7 +212,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory return new() { // history - HistoryWishes = new(historyWishes), + HistoryWishes = historyWishes.ToAdvancedCollectionView(), // avatars OrangeAvatars = orangeAvatarCounter.ToStatisticsList(), diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs index dbce927b..5b43cdb5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs @@ -21,9 +21,15 @@ internal sealed partial class GameAccountService : IGameAccountService private ObservableReorderableDbCollection? gameAccounts; - public ObservableReorderableDbCollection GameAccountCollection + public async ValueTask> GetGameAccountCollectionAsync() { - get => gameAccounts ??= gameDbService.GetGameAccountCollection(); + if (gameAccounts is null) + { + await taskContext.SwitchToBackgroundAsync(); + gameAccounts = gameDbService.GetGameAccountCollection(); + } + + return gameAccounts; } public async ValueTask DetectGameAccountAsync(SchemeType schemeType) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/IGameAccountService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/IGameAccountService.cs index c3c7da9e..6a7c3d99 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/IGameAccountService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/IGameAccountService.cs @@ -9,14 +9,14 @@ namespace Snap.Hutao.Service.Game.Account; internal interface IGameAccountService { - ObservableReorderableDbCollection GameAccountCollection { get; } - ValueTask AttachGameAccountToUidAsync(GameAccount gameAccount, string uid); GameAccount? DetectCurrentGameAccount(SchemeType schemeType); ValueTask DetectGameAccountAsync(SchemeType schemeType); + ValueTask> GetGameAccountCollectionAsync(); + ValueTask ModifyGameAccountAsync(GameAccount gameAccount); ValueTask RemoveGameAccountAsync(GameAccount gameAccount); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs index 9c011292..d604aeeb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs @@ -11,10 +11,6 @@ using Snap.Hutao.Service.Game.PathAbstraction; namespace Snap.Hutao.Service.Game; -/// -/// 游戏服务 -/// -[HighQuality] [ConstructorGenerated] [Injection(InjectAs.Singleton, typeof(IGameServiceFacade))] internal sealed partial class GameServiceFacade : IGameServiceFacade @@ -23,55 +19,46 @@ internal sealed partial class GameServiceFacade : IGameServiceFacade private readonly IGameAccountService gameAccountService; private readonly IGamePathService gamePathService; - /// - public ObservableReorderableDbCollection GameAccountCollection + public ValueTask> GetGameAccountCollectionAsync() { - get => gameAccountService.GameAccountCollection; + return gameAccountService.GetGameAccountCollectionAsync(); } - /// public ValueTask> GetGamePathAsync() { return gamePathService.SilentGetGamePathAsync(); } - /// public ChannelOptions GetChannelOptions() { return gameChannelOptionsService.GetChannelOptions(); } - /// public ValueTask DetectGameAccountAsync(SchemeType scheme) { return gameAccountService.DetectGameAccountAsync(scheme); } - /// public GameAccount? DetectCurrentGameAccount(SchemeType scheme) { return gameAccountService.DetectCurrentGameAccount(scheme); } - /// public ValueTask AttachGameAccountToUidAsync(GameAccount gameAccount, string uid) { return gameAccountService.AttachGameAccountToUidAsync(gameAccount, uid); } - /// public ValueTask ModifyGameAccountAsync(GameAccount gameAccount) { return gameAccountService.ModifyGameAccountAsync(gameAccount); } - /// public ValueTask RemoveGameAccountAsync(GameAccount gameAccount) { return gameAccountService.RemoveGameAccountAsync(gameAccount); } - /// public bool IsGameRunning() { return LaunchExecutionEnsureGameNotRunningHandler.IsGameRunning(out _); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameServiceFacade.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameServiceFacade.cs index fcff98b9..707a6c98 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameServiceFacade.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameServiceFacade.cs @@ -10,8 +10,6 @@ namespace Snap.Hutao.Service.Game; internal interface IGameServiceFacade { - ObservableReorderableDbCollection GameAccountCollection { get; } - ValueTask AttachGameAccountToUidAsync(GameAccount gameAccount, string uid); ValueTask DetectGameAccountAsync(SchemeType scheme); @@ -27,4 +25,6 @@ internal interface IGameServiceFacade ValueTask RemoveGameAccountAsync(GameAccount gameAccount); GameAccount? DetectCurrentGameAccount(SchemeType scheme); + + ValueTask> GetGameAccountCollectionAsync(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs index 612fec23..bcf53324 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Model.Entity.Extension; +using Snap.Hutao.UI.Xaml.Data; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Passport; @@ -213,7 +214,7 @@ internal sealed partial class UserInitializationService : IUserInitializationSer if (userGameRolesResponse.IsOk()) { - user.UserGameRoles = new(userGameRolesResponse.Data.List); + user.UserGameRoles = userGameRolesResponse.Data.List.ToAdvancedCollectionView(); return user.UserGameRoles.Count > 0; } else diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Control/Layout/UniformStaggeredLayoutState.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Control/Layout/UniformStaggeredLayoutState.cs index 6f31057d..5ec5f717 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Control/Layout/UniformStaggeredLayoutState.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Control/Layout/UniformStaggeredLayoutState.cs @@ -100,7 +100,11 @@ internal sealed class UniformStaggeredLayoutState internal void Clear() { - RecycleElements(); + if (items.Count > 0) + { + RecycleElements(); + } + ClearColumns(); ClearItems(); } diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Data/AdvancedCollectionView.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Data/AdvancedCollectionView.cs index ad9003c8..0253f741 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Data/AdvancedCollectionView.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Data/AdvancedCollectionView.cs @@ -4,7 +4,6 @@ using CommunityToolkit.WinUI.Collections; using CommunityToolkit.WinUI.Helpers; using Microsoft.UI.Xaml.Data; -using Snap.Hutao.Core.ExceptionService; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/AchievementImportDialog.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/AchievementImportDialog.xaml index 99c594df..4628fa30 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/AchievementImportDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/AchievementImportDialog.xaml @@ -60,7 +60,7 @@ -/// 成就对话框 -/// -[HighQuality] [DependencyProperty("UIAF", typeof(UIAF))] internal sealed partial class AchievementImportDialog : ContentDialog { private readonly ITaskContext taskContext; - /// - /// 构造一个新的成就对话框 - /// - /// 服务提供器 - /// uiaf数据 public AchievementImportDialog(IServiceProvider serviceProvider, UIAF uiaf) { InitializeComponent(); @@ -30,10 +21,6 @@ internal sealed partial class AchievementImportDialog : ContentDialog UIAF = uiaf; } - /// - /// 异步获取导入选项 - /// - /// 导入选项 public async ValueTask> GetImportStrategyAsync() { await taskContext.SwitchToMainThreadAsync(); diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml index d05cd114..17c63f7d 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml @@ -73,5 +73,14 @@ SpinButtonPlacementMode="Inline" Value="{x:Bind PromotionDelta.Weapon.LevelTarget, Mode=TwoWay}"/> + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs index f854aa44..88f1ee4a 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs @@ -3,6 +3,7 @@ using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Core.Setting; +using Snap.Hutao.Service.Cultivation; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; namespace Snap.Hutao.UI.Xaml.View.Dialog; @@ -21,7 +22,7 @@ internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog PromotionDelta = AvatarPromotionDelta.CreateForBaseline(); } - public async ValueTask> GetPromotionDeltaBaselineAsync() + public async ValueTask> GetPromotionDeltaBaselineAsync() { await taskContext.SwitchToMainThreadAsync(); ContentDialogResult result = await ShowAsync(); @@ -50,6 +51,6 @@ internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog LocalSetting.Set(SettingKeys.CultivationWeapon90LevelTarget, weapon.LevelTarget); } - return new(true, PromotionDelta); + return new(true, new(PromotionDelta, (ConsumptionSaveStrategyKind)SaveModeSelector.SelectedIndex)); } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaDialog.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaDialog.xaml index c1539985..241b706c 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaDialog.xaml @@ -64,16 +64,14 @@ - + + - + - + + + + + + - + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs index 7a6c9433..c337a775 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs @@ -4,25 +4,17 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Model.Calculable; +using Snap.Hutao.Service.Cultivation; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; namespace Snap.Hutao.UI.Xaml.View.Dialog; -/// -/// 养成计算对话框 -/// -[HighQuality] [DependencyProperty("Avatar", typeof(ICalculableAvatar))] [DependencyProperty("Weapon", typeof(ICalculableWeapon))] internal sealed partial class CultivatePromotionDeltaDialog : ContentDialog { private readonly ITaskContext taskContext; - /// - /// 构造一个新的养成计算对话框 - /// - /// 服务提供器 - /// 选项 public CultivatePromotionDeltaDialog(IServiceProvider serviceProvider, CalculableOptions options) { InitializeComponent(); @@ -33,11 +25,7 @@ internal sealed partial class CultivatePromotionDeltaDialog : ContentDialog Weapon = options.Weapon; } - /// - /// 异步获取提升差异 - /// - /// 提升差异 - public async ValueTask> GetPromotionDeltaAsync() + public async ValueTask> GetPromotionDeltaAsync() { await taskContext.SwitchToMainThreadAsync(); ContentDialogResult result = await ShowAsync(); @@ -66,6 +54,6 @@ internal sealed partial class CultivatePromotionDeltaDialog : ContentDialog }, }; - return new(true, delta); + return new(true, new(delta, (ConsumptionSaveStrategyKind)SaveModeSelector.SelectedIndex)); } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaOptions.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaOptions.cs new file mode 100644 index 00000000..859ec098 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaOptions.cs @@ -0,0 +1,20 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Service.Cultivation; +using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; + +namespace Snap.Hutao.UI.Xaml.View.Dialog; + +internal sealed class CultivatePromotionDeltaOptions +{ + public CultivatePromotionDeltaOptions(AvatarPromotionDelta delta, ConsumptionSaveStrategyKind strategy) + { + Delta = delta; + Strategy = strategy; + } + + public AvatarPromotionDelta Delta { get; set; } + + public ConsumptionSaveStrategyKind Strategy { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UIGFUidSelection.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UIGFUidSelection.cs similarity index 100% rename from src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UIGFUidSelection.xaml.cs rename to src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/UIGFUidSelection.cs diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs index 02117d00..e6e6fab5 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs @@ -107,13 +107,15 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav /// public async ValueTask ReceiveAsync(INavigationData data) { - if (await Initialization.Task.ConfigureAwait(false)) + if (!await Initialization.Task.ConfigureAwait(false)) { - if (data.Data is AppActivation.ImportUIAFFromClipboard) - { - await ImportUIAFFromClipboardAsync().ConfigureAwait(false); - return true; - } + return false; + } + + if (data.Data is AppActivation.ImportUIAFFromClipboard) + { + await ImportUIAFFromClipboardAsync().ConfigureAwait(false); + return true; } return false; @@ -206,7 +208,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav [Command("RemoveArchiveCommand")] private async Task RemoveArchiveAsync() { - if (Archives is null || !(Archives.CurrentItem is { } current)) + if (Archives?.CurrentItem is not { } current) { return; } @@ -283,7 +285,9 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav private async ValueTask UpdateAchievementsAsync(EntityArchive? archive) { - // TODO: immediately clear values + await scopeContext.TaskContext.SwitchToMainThreadAsync(); + Achievements = default; + if (archive is null) { return; @@ -293,31 +297,29 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav .GetContextAsync(CancellationToken) .ConfigureAwait(false); - if (!TryGetAchievements(archive, context, out List? combined)) + if (!TryGetAchievements(archive, context, out AdvancedCollectionView? combined)) { return; } - AdvancedCollectionView achievements = new(combined); - await scopeContext.TaskContext.SwitchToMainThreadAsync(); - Achievements = achievements; + Achievements = combined; AchievementFinishPercent.Update(this); UpdateAchievementsFilterByGoal(AchievementGoals?.CurrentItem); UpdateAchievementsSort(); } - private bool TryGetAchievements(EntityArchive archive, AchievementServiceMetadataContext context, [NotNullWhen(true)] out List? combined) + private bool TryGetAchievements(EntityArchive archive, AchievementServiceMetadataContext context, [NotNullWhen(true)] out AdvancedCollectionView? view) { try { - combined = scopeContext.AchievementService.GetAchievementViewList(archive, context); + view = scopeContext.AchievementService.GetAchievementViewList(archive, context).ToAdvancedCollectionView(); return true; } catch (HutaoException ex) { scopeContext.InfoBarService.Error(ex); - combined = default; + view = default; return false; } } @@ -330,33 +332,41 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav return; } - Achievements.SortDescriptions.Clear(); - AchievementGoals.SortDescriptions.Clear(); - - if (IsUncompletedItemsFirst) + using (Achievements.DeferRefresh()) { - Achievements.SortDescriptions.Add(achievementUncompletedItemsFirstSortDescription); - Achievements.SortDescriptions.Add(achievementCompletionTimeSortDescription); - AchievementGoals.SortDescriptions.Add(achievementGoalUncompletedItemsFirstSortDescription); - } + using (AchievementGoals.DeferRefresh()) + { + Achievements.SortDescriptions.Clear(); + AchievementGoals.SortDescriptions.Clear(); - Achievements.SortDescriptions.Add(achievementDefaultSortDescription); - AchievementGoals.SortDescriptions.Add(achievementGoalDefaultSortDescription); + if (IsUncompletedItemsFirst) + { + Achievements.SortDescriptions.Add(achievementUncompletedItemsFirstSortDescription); + Achievements.SortDescriptions.Add(achievementCompletionTimeSortDescription); + AchievementGoals.SortDescriptions.Add(achievementGoalUncompletedItemsFirstSortDescription); + } + + Achievements.SortDescriptions.Add(achievementDefaultSortDescription); + AchievementGoals.SortDescriptions.Add(achievementGoalDefaultSortDescription); + } + } } private void UpdateAchievementsFilterByGoal(AchievementGoalView? goal) { - if (Achievements is not null) + if (Achievements is null) { - if (goal is null) - { - Achievements.Filter = default!; - } - else - { - Model.Primitive.AchievementGoalId goalId = goal.Id; - Achievements.Filter = (AchievementView view) => view.Inner.Goal == goalId; - } + return; + } + + if (goal is null) + { + Achievements.Filter = default!; + } + else + { + Model.Primitive.AchievementGoalId goalId = goal.Id; + Achievements.Filter = (AchievementView view) => view.Inner.Goal == goalId; } } @@ -398,10 +408,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav [Command("SaveAchievementCommand")] private void SaveAchievement(AchievementView? achievement) { - if (achievement is not null) + if (achievement is null) { - scopeContext.AchievementService.SaveAchievement(achievement); - AchievementFinishPercent.Update(this); + return; } + + scopeContext.AchievementService.SaveAchievement(achievement); + AchievementFinishPercent.Update(this); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs index 4ffd0a3f..cb55f5d0 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs @@ -3,9 +3,7 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.UI.Xaml.Controls; -using Snap.Hutao.Core.DataTransfer; using Snap.Hutao.Core.ExceptionService; -using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Model.Calculable; using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Service.AvatarInfo; @@ -20,9 +18,7 @@ using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using Snap.Hutao.Web.Response; using CalculatorAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculatorBatchConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.BatchConsumption; -using CalculatorClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; using CalculatorConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption; -using CalculatorItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item; using CalculatorItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper; namespace Snap.Hutao.ViewModel.AvatarProperty; @@ -31,14 +27,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty; [Injection(InjectAs.Scoped)] internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, IRecipient { - private readonly IContentDialogFactory contentDialogFactory; - private readonly ICultivationService cultivationService; - private readonly IAvatarInfoService avatarInfoService; - private readonly IClipboardProvider clipboardProvider; - private readonly CalculatorClient calculatorClient; - private readonly IInfoBarService infoBarService; - private readonly ITaskContext taskContext; - private readonly IUserService userService; + private readonly AvatarPropertyViewModelScopeContext scopeContext; private Summary? summary; @@ -61,7 +50,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I protected override async ValueTask InitializeOverrideAsync() { - if (await userService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is { } userAndUid) + if (await scopeContext.UserService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is { } userAndUid) { await RefreshCoreAsync(userAndUid, RefreshOption.None, CancellationToken).ConfigureAwait(false); return true; @@ -73,7 +62,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I [Command("RefreshFromEnkaApiCommand")] private async Task RefreshByEnkaApiAsync() { - if (await userService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is { } userAndUid) + if (await scopeContext.UserService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is { } userAndUid) { await RefreshCoreAsync(userAndUid, RefreshOption.RequestFromEnkaAPI, CancellationToken).ConfigureAwait(false); } @@ -82,7 +71,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I [Command("RefreshFromHoyolabGameRecordCommand")] private async Task RefreshByHoyolabGameRecordAsync() { - if (await userService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is { } userAndUid) + if (await scopeContext.UserService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is { } userAndUid) { await RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabGameRecord, CancellationToken).ConfigureAwait(false); } @@ -91,7 +80,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I [Command("RefreshFromHoyolabCalculateCommand")] private async Task RefreshByHoyolabCalculateAsync() { - if (await userService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is { } userAndUid) + if (await scopeContext.UserService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is { } userAndUid) { await RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabCalculate, CancellationToken).ConfigureAwait(false); } @@ -101,18 +90,19 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I { try { - await taskContext.SwitchToMainThreadAsync(); + await scopeContext.TaskContext.SwitchToMainThreadAsync(); IsInitialized = false; + ValueResult summaryResult; using (await EnterCriticalSectionAsync().ConfigureAwait(false)) { - ContentDialog dialog = await contentDialogFactory + ContentDialog dialog = await scopeContext.ContentDialogFactory .CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyFetch) .ConfigureAwait(false); - using (await dialog.BlockAsync(taskContext).ConfigureAwait(false)) + using (await dialog.BlockAsync(scopeContext.TaskContext).ConfigureAwait(false)) { - summaryResult = await avatarInfoService + summaryResult = await scopeContext.AvatarInfoService .GetSummaryAsync(userAndUid, option, token) .ConfigureAwait(false); } @@ -121,7 +111,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I (RefreshResultKind result, Summary? summary) = summaryResult; if (result is RefreshResultKind.Ok) { - await taskContext.SwitchToMainThreadAsync(); + await scopeContext.TaskContext.SwitchToMainThreadAsync(); Summary = summary; Summary?.Avatars.MoveCurrentToFirstOrDefault(); } @@ -130,16 +120,16 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I switch (result) { case RefreshResultKind.APIUnavailable: - infoBarService.Warning(SH.ViewModelAvatarPropertyEnkaApiUnavailable); + scopeContext.InfoBarService.Warning(SH.ViewModelAvatarPropertyEnkaApiUnavailable); break; case RefreshResultKind.StatusCodeNotSucceed: ArgumentNullException.ThrowIfNull(summary); - infoBarService.Warning(summary.Message); + scopeContext.InfoBarService.Warning(summary.Message); break; case RefreshResultKind.ShowcaseNotOpen: - infoBarService.Warning(SH.ViewModelAvatarPropertyShowcaseNotOpen); + scopeContext.InfoBarService.Warning(SH.ViewModelAvatarPropertyShowcaseNotOpen); break; } } @@ -149,7 +139,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I } finally { - await taskContext.SwitchToMainThreadAsync(); + await scopeContext.TaskContext.SwitchToMainThreadAsync(); IsInitialized = true; } } @@ -162,41 +152,47 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I return; } - if (await userService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is not { } userAndUid) + if (await scopeContext.UserService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is not { } userAndUid) { - infoBarService.Warning(SH.MustSelectUserAndUid); + scopeContext.InfoBarService.Warning(SH.MustSelectUserAndUid); return; } if (avatar.Weapon is null) { - infoBarService.Warning(SH.ViewModelAvatarPropertyCalculateWeaponNull); + scopeContext.InfoBarService.Warning(SH.ViewModelAvatarPropertyCalculateWeaponNull); return; } CalculableOptions options = new(avatar.ToCalculable(), avatar.Weapon.ToCalculable()); - CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync(options).ConfigureAwait(false); - (bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); + CultivatePromotionDeltaDialog dialog = await scopeContext.ContentDialogFactory + .CreateInstanceAsync(options).ConfigureAwait(false); + (bool isOk, CultivatePromotionDeltaOptions deltaOptions) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); if (!isOk) { return; } - Response response = await calculatorClient.BatchComputeAsync(userAndUid, delta).ConfigureAwait(false); + Response response; + using (IServiceScope scope = scopeContext.ServiceScopeFactory.CreateScope()) + { + CalculateClient calculatorClient = scope.ServiceProvider.GetRequiredService(); + response = await calculatorClient.BatchComputeAsync(userAndUid, deltaOptions.Delta).ConfigureAwait(false); + } if (!response.IsOk()) { return; } - if (!await SaveCultivationAsync(response.Data.Items.Single(), delta).ConfigureAwait(false)) + if (!await SaveCultivationAsync(response.Data.Items.Single(), deltaOptions).ConfigureAwait(false)) { - infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); + scopeContext.InfoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); return; } - infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); + scopeContext.InfoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); } [Command("BatchCultivateCommand")] @@ -207,34 +203,35 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I return; } - if (await userService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is not { } userAndUid) + if (await scopeContext.UserService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is not { } userAndUid) { - infoBarService.Warning(SH.MustSelectUserAndUid); + scopeContext.InfoBarService.Warning(SH.MustSelectUserAndUid); return; } - CultivatePromotionDeltaBatchDialog dialog = await contentDialogFactory.CreateInstanceAsync().ConfigureAwait(false); - (bool isOk, CalculatorAvatarPromotionDelta baseline) = await dialog.GetPromotionDeltaBaselineAsync().ConfigureAwait(false); + CultivatePromotionDeltaBatchDialog dialog = await scopeContext.ContentDialogFactory + .CreateInstanceAsync().ConfigureAwait(false); + (bool isOk, CultivatePromotionDeltaOptions deltaOptions) = await dialog.GetPromotionDeltaBaselineAsync().ConfigureAwait(false); if (!isOk) { return; } - ArgumentNullException.ThrowIfNull(baseline.SkillList); - ArgumentNullException.ThrowIfNull(baseline.Weapon); + ArgumentNullException.ThrowIfNull(deltaOptions.Delta.SkillList); + ArgumentNullException.ThrowIfNull(deltaOptions.Delta.Weapon); - ContentDialog progressDialog = await contentDialogFactory + ContentDialog progressDialog = await scopeContext.ContentDialogFactory .CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyBatchCultivateProgressTitle) .ConfigureAwait(false); BatchCultivateResult result = default; - using (await progressDialog.BlockAsync(taskContext).ConfigureAwait(false)) + using (await progressDialog.BlockAsync(scopeContext.TaskContext).ConfigureAwait(false)) { List deltas = []; foreach (AvatarView avatar in avatars) { - if (!baseline.TryGetNonErrorCopy(avatar, out CalculatorAvatarPromotionDelta? copy)) + if (!deltaOptions.Delta.TryGetNonErrorCopy(avatar, out CalculatorAvatarPromotionDelta? copy)) { ++result.SkippedCount; continue; @@ -243,7 +240,12 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I deltas.Add(copy); } - Response response = await calculatorClient.BatchComputeAsync(userAndUid, deltas).ConfigureAwait(false); + Response response; + using (IServiceScope scope = scopeContext.ServiceScopeFactory.CreateScope()) + { + CalculateClient calculatorClient = scope.ServiceProvider.GetRequiredService(); + response = await calculatorClient.BatchComputeAsync(userAndUid, deltas).ConfigureAwait(false); + } if (!response.IsOk()) { @@ -252,9 +254,8 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I foreach ((CalculatorConsumption consumption, CalculatorAvatarPromotionDelta delta) in response.Data.Items.Zip(deltas)) { - if (!await SaveCultivationAsync(consumption, delta).ConfigureAwait(false)) + if (!await SaveCultivationAsync(consumption, new(delta, deltaOptions.Strategy)).ConfigureAwait(false)) { - result.Interrupted = true; break; } @@ -262,42 +263,70 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I } } - if (result.Interrupted) + if (result.SkippedCount > 0) { - infoBarService.Warning(SH.FormatViewModelCultivationBatchAddIncompletedFormat(result.SucceedCount, result.SkippedCount)); + scopeContext.InfoBarService.Warning(SH.FormatViewModelCultivationBatchAddIncompletedFormat(result.SucceedCount, result.SkippedCount)); } else { - infoBarService.Success(SH.FormatViewModelCultivationBatchAddCompletedFormat(result.SucceedCount, result.SkippedCount)); + scopeContext.InfoBarService.Success(SH.FormatViewModelCultivationBatchAddCompletedFormat(result.SucceedCount, result.SkippedCount)); } } - private async ValueTask SaveCultivationAsync(CalculatorConsumption consumption, CalculatorAvatarPromotionDelta delta) + /// if we can continue saving consumptions, otherwise . + private async ValueTask SaveCultivationAsync(CalculatorConsumption consumption, CultivatePromotionDeltaOptions options) { - LevelInformation levelInformation = LevelInformation.From(delta); + LevelInformation levelInformation = LevelInformation.From(options.Delta); - List items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); - bool avatarSaved = await cultivationService - .SaveConsumptionAsync(CultivateType.AvatarAndSkill, delta.AvatarId, items, levelInformation) - .ConfigureAwait(false); + InputConsumption avatarInput = new() + { + Type = CultivateType.AvatarAndSkill, + ItemId = options.Delta.AvatarId, + Items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume), + LevelInformation = levelInformation, + Strategy = options.Strategy, + }; + + ConsumptionSaveResultKind avatarSaveKind = await scopeContext.CultivationService.SaveConsumptionAsync(avatarInput).ConfigureAwait(false); + + switch (avatarSaveKind) + { + case ConsumptionSaveResultKind.NoProject: + scopeContext.InfoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); + return false; + case ConsumptionSaveResultKind.Skipped: + scopeContext.InfoBarService.Information(SH.ViewModelCultivationConsumptionSaveSkippedHint); + break; + case ConsumptionSaveResultKind.NoItem: + scopeContext.InfoBarService.Information(SH.ViewModelCultivationConsumptionSaveNoItemHint); + break; + case ConsumptionSaveResultKind.Added: + break; + } try { - ArgumentNullException.ThrowIfNull(delta.Weapon); + ArgumentNullException.ThrowIfNull(options.Delta.Weapon); - // Take a hot path if avatar is not saved. - bool avatarAndWeaponSaved = avatarSaved && await cultivationService - .SaveConsumptionAsync(CultivateType.Weapon, delta.Weapon.Id, consumption.WeaponConsume.EmptyIfNull(), levelInformation) - .ConfigureAwait(false); + InputConsumption weaponInput = new() + { + Type = CultivateType.Weapon, + ItemId = options.Delta.Weapon.Id, + Items = consumption.WeaponConsume.EmptyIfNull(), + LevelInformation = levelInformation, + Strategy = options.Strategy, + }; - return avatarAndWeaponSaved; + ConsumptionSaveResultKind weaponSaveKind = await scopeContext.CultivationService.SaveConsumptionAsync(weaponInput).ConfigureAwait(false); + + return weaponSaveKind is not ConsumptionSaveResultKind.NoProject; } catch (HutaoException ex) { - infoBarService.Error(ex, SH.ViewModelCultivationAddWarning); + scopeContext.InfoBarService.Error(ex, SH.ViewModelCultivationAddWarning); } - return true; + return false; } [Command("ExportToTextCommand")] @@ -308,13 +337,13 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I return; } - if (clipboardProvider.SetText(AvatarViewTextTemplating.GetTemplatedText(avatar))) + if (scopeContext.ClipboardProvider.SetText(AvatarViewTextTemplating.GetTemplatedText(avatar))) { - infoBarService.Success(SH.ViewModelAvatatPropertyExportTextSuccess); + scopeContext.InfoBarService.Success(SH.ViewModelAvatatPropertyExportTextSuccess); } else { - infoBarService.Warning(SH.ViewModelAvatatPropertyExportTextError); + scopeContext.InfoBarService.Warning(SH.ViewModelAvatatPropertyExportTextError); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModelScopeContext.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModelScopeContext.cs new file mode 100644 index 00000000..e2baf5e7 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModelScopeContext.cs @@ -0,0 +1,41 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.DataTransfer; +using Snap.Hutao.Factory.ContentDialog; +using Snap.Hutao.Service.AvatarInfo; +using Snap.Hutao.Service.Cultivation; +using Snap.Hutao.Service.Notification; +using Snap.Hutao.Service.User; + +namespace Snap.Hutao.ViewModel.AvatarProperty; + +[ConstructorGenerated] +[Injection(InjectAs.Scoped)] +internal sealed partial class AvatarPropertyViewModelScopeContext +{ + private readonly IContentDialogFactory contentDialogFactory; + private readonly IServiceScopeFactory serviceScopeFactory; + private readonly ICultivationService cultivationService; + private readonly IAvatarInfoService avatarInfoService; + private readonly IClipboardProvider clipboardProvider; + private readonly IInfoBarService infoBarService; + private readonly ITaskContext taskContext; + private readonly IUserService userService; + + public IContentDialogFactory ContentDialogFactory { get => contentDialogFactory; } + + public IServiceScopeFactory ServiceScopeFactory { get => serviceScopeFactory; } + + public ICultivationService CultivationService { get => cultivationService; } + + public IAvatarInfoService AvatarInfoService { get => avatarInfoService; } + + public IClipboardProvider ClipboardProvider { get => clipboardProvider; } + + public IInfoBarService InfoBarService { get => infoBarService; } + + public ITaskContext TaskContext { get => taskContext; } + + public IUserService UserService { get => userService; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/BatchCultivateResult.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/BatchCultivateResult.cs index 1d24a185..895068c6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/BatchCultivateResult.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/BatchCultivateResult.cs @@ -7,5 +7,4 @@ internal struct BatchCultivateResult { public int SucceedCount; public int SkippedCount; - public bool Interrupted; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index 24f76e87..816eb8d5 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -325,7 +325,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView async ValueTask UpdateGameAccountsViewAsync() { gameAccountFilter = new(SelectedScheme?.GetSchemeType()); - ObservableReorderableDbCollection accounts = gameService.GameAccountCollection; + ObservableReorderableDbCollection accounts = await gameService.GetGameAccountCollectionAsync().ConfigureAwait(false); AdvancedCollectionView accountsView = new(accounts) { Filter = gameAccountFilter.Filter }; await taskContext.SwitchToMainThreadAsync(); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs index 3bc49e15..1825ee16 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs @@ -43,13 +43,14 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli protected override async Task LoadAsync() { LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile(); - ObservableCollection accounts = gameService.GameAccountCollection; + ObservableCollection accounts = await gameService.GetGameAccountCollectionAsync().ConfigureAwait(false); try { if (scheme is not null) { // Try set to the current account. + await taskContext.SwitchToMainThreadAsync(); SelectedGameAccount ??= gameService.DetectCurrentGameAccount(scheme); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingGachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingGachaLogViewModel.cs index 2b93fe7f..b6fb3869 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingGachaLogViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingGachaLogViewModel.cs @@ -4,7 +4,6 @@ using Snap.Hutao.Core.IO; using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Factory.Picker; -using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.InterChange.GachaLog; using Snap.Hutao.Service; using Snap.Hutao.Service.GachaLog; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssRecordViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssRecordViewModel.cs index 042466d0..4ec178ab 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssRecordViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssRecordViewModel.cs @@ -85,7 +85,7 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel .ConfigureAwait(false); } - AdvancedCollectionView spiralAbyssEntries = new(collection); + AdvancedCollectionView spiralAbyssEntries = collection.ToAdvancedCollectionView(); await taskContext.SwitchToMainThreadAsync(); SpiralAbyssEntries = spiralAbyssEntries; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index 626523ef..f09c0910 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -24,7 +24,6 @@ using Snap.Hutao.Web.Response; using System.Collections.Frozen; using System.Collections.ObjectModel; using System.Runtime.InteropServices; -using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculateBatchConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.BatchConsumption; using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; @@ -43,8 +42,8 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel private readonly IMetadataService metadataService; private readonly ITaskContext taskContext; private readonly IHutaoSpiralAbyssStatisticsCache hutaoCache; + private readonly IServiceScopeFactory serviceScopeFactory; private readonly IInfoBarService infoBarService; - private readonly CalculateClient calculateClient; private readonly IUserService userService; private AdvancedCollectionView? avatars; @@ -109,7 +108,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel using (await EnterCriticalSectionAsync().ConfigureAwait(false)) { - AdvancedCollectionView avatarsView = new(list); + AdvancedCollectionView avatarsView = list.ToAdvancedCollectionView(); await taskContext.SwitchToMainThreadAsync(); Avatars = avatarsView; @@ -173,16 +172,19 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel CalculableOptions options = new(avatar.ToCalculable(), null); CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync(options).ConfigureAwait(false); - (bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); + (bool isOk, CultivatePromotionDeltaOptions deltaOptions) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); if (!isOk) { return; } - Response response = await calculateClient - .BatchComputeAsync(userAndUid, delta) - .ConfigureAwait(false); + Response response; + using (IServiceScope scope = serviceScopeFactory.CreateScope()) + { + CalculateClient calculateClient = scope.ServiceProvider.GetRequiredService(); + response = await calculateClient.BatchComputeAsync(userAndUid, deltaOptions.Delta).ConfigureAwait(false); + } if (!response.IsOk()) { @@ -190,20 +192,32 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel } CalculateBatchConsumption batchConsumption = response.Data; - LevelInformation levelInformation = LevelInformation.From(delta); + LevelInformation levelInformation = LevelInformation.From(deltaOptions.Delta); try { - bool saved = await cultivationService - .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, batchConsumption.OverallConsume, levelInformation) - .ConfigureAwait(false); + InputConsumption input = new() + { + Type = CultivateType.AvatarAndSkill, + ItemId = avatar.Id, + Items = batchConsumption.OverallConsume, + LevelInformation = levelInformation, + Strategy = deltaOptions.Strategy, + }; - if (saved) + switch (await cultivationService.SaveConsumptionAsync(input).ConfigureAwait(false)) { - infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); - } - else - { - infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); + case ConsumptionSaveResultKind.NoProject: + infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); + break; + case ConsumptionSaveResultKind.Skipped: + infoBarService.Information(SH.ViewModelCultivationConsumptionSaveSkippedHint); + break; + case ConsumptionSaveResultKind.NoItem: + infoBarService.Information(SH.ViewModelCultivationConsumptionSaveNoItemHint); + break; + case ConsumptionSaveResultKind.Added: + infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); + break; } } catch (HutaoException ex) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs index 74e1d10c..14dd4f2c 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs @@ -68,7 +68,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel using (await EnterCriticalSectionAsync().ConfigureAwait(false)) { - AdvancedCollectionView monstersView = new(ordered); + AdvancedCollectionView monstersView = ordered.ToAdvancedCollectionView(); await taskContext.SwitchToMainThreadAsync(); Monsters = monstersView; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs index 1a0cec91..cbe33de6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -24,7 +24,6 @@ using Snap.Hutao.Web.Response; using System.Collections.Frozen; using System.Collections.ObjectModel; using System.Runtime.InteropServices; -using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculateBatchConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.BatchConsumption; using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; @@ -38,11 +37,11 @@ namespace Snap.Hutao.ViewModel.Wiki; internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel { private readonly IContentDialogFactory contentDialogFactory; - private readonly CalculateClient calculateClient; private readonly ICultivationService cultivationService; private readonly ITaskContext taskContext; private readonly IMetadataService metadataService; private readonly IHutaoSpiralAbyssStatisticsCache hutaoCache; + private readonly IServiceScopeFactory serviceScopeFactory; private readonly IInfoBarService infoBarService; private readonly IUserService userService; @@ -108,7 +107,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel using (await EnterCriticalSectionAsync().ConfigureAwait(false)) { - AdvancedCollectionView weaponsView = new(list); + AdvancedCollectionView weaponsView = list.ToAdvancedCollectionView(); await taskContext.SwitchToMainThreadAsync(); @@ -168,16 +167,19 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel CalculableOptions options = new(null, weapon.ToCalculable()); CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync(options).ConfigureAwait(false); - (bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); + (bool isOk, CultivatePromotionDeltaOptions deltaOptions) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); if (!isOk) { return; } - Response response = await calculateClient - .BatchComputeAsync(userAndUid, delta) - .ConfigureAwait(false); + Response response; + using (IServiceScope scope = serviceScopeFactory.CreateScope()) + { + CalculateClient calculateClient = scope.ServiceProvider.GetRequiredService(); + response = await calculateClient.BatchComputeAsync(userAndUid, deltaOptions.Delta).ConfigureAwait(false); + } if (!response.IsOk()) { @@ -185,20 +187,32 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel } CalculateBatchConsumption batchConsumption = response.Data; - LevelInformation levelInformation = LevelInformation.From(delta); + LevelInformation levelInformation = LevelInformation.From(deltaOptions.Delta); try { - bool saved = await cultivationService - .SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, batchConsumption.OverallConsume, levelInformation) - .ConfigureAwait(false); + InputConsumption input = new() + { + Type = CultivateType.Weapon, + ItemId = weapon.Id, + Items = batchConsumption.OverallConsume, + LevelInformation = levelInformation, + Strategy = deltaOptions.Strategy, + }; - if (saved) + switch (await cultivationService.SaveConsumptionAsync(input).ConfigureAwait(false)) { - infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); - } - else - { - infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); + case ConsumptionSaveResultKind.NoProject: + infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); + break; + case ConsumptionSaveResultKind.Skipped: + infoBarService.Information(SH.ViewModelCultivationConsumptionSaveSkippedHint); + break; + case ConsumptionSaveResultKind.NoItem: + infoBarService.Information(SH.ViewModelCultivationConsumptionSaveNoItemHint); + break; + case ConsumptionSaveResultKind.Added: + infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); + break; } } catch (HutaoException ex)