diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx index 4b1c543..3c70037 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx @@ -1349,6 +1349,9 @@ Space Available: {2} Weapon Target Level + + Clear existing character and weapon entries before updating + Enter the plan name here diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index e8536bd..1701d4e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -1391,6 +1391,9 @@ 武器目标等级 + + 更新计划前先清空已有角色与武器条目 + 在此处输入计划名称 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs index bdffcd1..9a0e04d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -178,6 +178,48 @@ internal sealed partial class CultivationService : ICultivationService cultivationRepository.RemoveCultivateEntryById(entryId); } + public async ValueTask RemoveAvatarAndWeaponEntriesForCurrentProjectAsync() + { + IAdvancedDbCollectionView projects = await GetProjectCollectionAsync().ConfigureAwait(false); + if (!await EnsureCurrentProjectAsync(projects).ConfigureAwait(false)) + { + return; + } + + ArgumentNullException.ThrowIfNull(projects.CurrentItem); + Guid projectId = projects.CurrentItem.InnerId; + + await taskContext.SwitchToBackgroundAsync(); + + ImmutableArray entries = cultivationRepository.GetCultivateEntryImmutableArrayByProjectId(projectId); + List weaponEntryIds = new(entries.Length); + List avatarEntryIds = new(entries.Length); + foreach (ref readonly CultivateEntry entry in entries.AsSpan()) + { + switch (entry.Type) + { + case CultivateType.Weapon: + weaponEntryIds.Add(entry.InnerId); + break; + case CultivateType.AvatarAndSkill: + avatarEntryIds.Add(entry.InnerId); + break; + } + } + + foreach (Guid id in weaponEntryIds) + { + cultivationRepository.RemoveCultivateEntryById(id); + } + + foreach (Guid id in avatarEntryIds) + { + cultivationRepository.RemoveCultivateEntryById(id); + } + + entryCollectionCache.TryRemove(projectId, out _); + } + public void SaveCultivateItem(CultivateItemView item) { cultivationRepository.UpdateCultivateItem(item.Entity); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs index ee00ee3..7daf318 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs @@ -23,6 +23,11 @@ internal interface ICultivationService ValueTask RemoveCultivateEntryAsync(Guid entryId); + /// + /// 移除当前选中养成计划中角色与武器类型的全部养成条目。 + /// + ValueTask RemoveAvatarAndWeaponEntriesForCurrentProjectAsync(); + ValueTask RemoveProjectAsync(CultivateProject project); ValueTask SaveConsumptionAsync(InputConsumption inputConsumption); 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 0d0e0e7..639212a 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 @@ -77,5 +77,10 @@ + + \ No newline at end of file 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 a5d33ab..00aa35e 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 @@ -10,6 +10,7 @@ using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; namespace Snap.Hutao.UI.Xaml.View.Dialog; [DependencyProperty("PromotionDelta", NotNull = true, CreateDefaultValueCallbackName = nameof(CreatePromotionDeltaDefaultValue))] +[DependencyProperty("ClearAvatarAndWeaponEntriesBeforeSync", DefaultValue = false, NotNull = true)] internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog { private readonly IContentDialogFactory contentDialogFactory; @@ -45,7 +46,7 @@ internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog LocalSetting.Set(SettingKeys.CultivationWeapon90LevelTarget, weapon.LevelTarget); } - return new(true, new(PromotionDelta, (ConsumptionSaveStrategyKind)SaveModeSelector.SelectedIndex)); + return new(true, new CultivatePromotionDeltaOptions(PromotionDelta, (ConsumptionSaveStrategyKind)SaveModeSelector.SelectedIndex, ClearAvatarAndWeaponEntriesBeforeSync)); } private static object CreatePromotionDeltaDefaultValue() 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 index 621d687..f374f64 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaOptions.cs @@ -8,7 +8,7 @@ namespace Snap.Hutao.UI.Xaml.View.Dialog; internal sealed class CultivatePromotionDeltaOptions { - public CultivatePromotionDeltaOptions(AvatarPromotionDelta delta, ConsumptionSaveStrategyKind strategy) + public CultivatePromotionDeltaOptions(AvatarPromotionDelta delta, ConsumptionSaveStrategyKind strategy, bool clearAvatarAndWeaponEntriesBeforeSync = false) { delta.AvatarLevelTarget = delta.AvatarLevelTarget switch { @@ -19,9 +19,15 @@ internal sealed class CultivatePromotionDeltaOptions Delta = delta; Strategy = strategy; + ClearAvatarAndWeaponEntriesBeforeSync = clearAvatarAndWeaponEntriesBeforeSync; } public AvatarPromotionDelta Delta { get; } public ConsumptionSaveStrategyKind Strategy { get; } + + /// + /// 批量同步前是否清空当前计划中已有的角色与武器养成条目(不含家具等其它类型)。 + /// + public bool ClearAvatarAndWeaponEntriesBeforeSync { get; } } \ 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 a8c20a3..2aa581a 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs @@ -21,6 +21,7 @@ using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.UI.Xaml.Control.AutoSuggestBox; using Snap.Hutao.UI.Xaml.View.Dialog; +using Snap.Hutao.ViewModel.Cultivation; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using System.Collections.Immutable; @@ -286,6 +287,11 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I BatchCultivateResult result = default; using (await scopeContext.ContentDialogFactory.BlockAsync(progressDialog).ConfigureAwait(false)) { + if (baseline.ClearAvatarAndWeaponEntriesBeforeSync) + { + await scopeContext.CultivationService.RemoveAvatarAndWeaponEntriesForCurrentProjectAsync().ConfigureAwait(false); + } + ImmutableArray.Builder deltasBuilder = ImmutableArray.CreateBuilder(); foreach (AvatarView avatar in targetAvatars) { @@ -305,7 +311,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I foreach ((CalculatorConsumption consumption, CalculatorAvatarPromotionDelta delta) in batchConsumption.Items.Zip(deltas)) { - if (!await SaveCultivationAsync(consumption, new(delta, baseline.Strategy), true).ConfigureAwait(false)) + if (!await SaveCultivationAsync(consumption, new CultivatePromotionDeltaOptions(delta, baseline.Strategy), true).ConfigureAwait(false)) { break; } @@ -314,6 +320,8 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I } } + scopeContext.Messenger.Send(CultivationProjectEntriesChangedMessage.Empty); + InfoBarMessage message = result.SkippedCount > 0 ? InfoBarMessage.Warning(SH.FormatViewModelCultivationBatchAddIncompleted(result.SucceedCount, result.SkippedCount)) : InfoBarMessage.Success(SH.FormatViewModelCultivationBatchAddCompleted(result.SucceedCount, result.SkippedCount)); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationProjectEntriesChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationProjectEntriesChangedMessage.cs new file mode 100644 index 0000000..95f0804 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationProjectEntriesChangedMessage.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.ViewModel.Cultivation; + +/// +/// 当前养成计划的条目在 UI 外被批量变更(例如从「我的角色」同步)后,通知养成页刷新列表与统计。 +/// +internal sealed class CultivationProjectEntriesChangedMessage +{ + public static readonly CultivationProjectEntriesChangedMessage Empty = new(); + + private CultivationProjectEntriesChangedMessage() + { + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs index 8cd2574..e4d79e0 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Core; using Snap.Hutao.Core.Database; @@ -29,7 +30,7 @@ namespace Snap.Hutao.ViewModel.Cultivation; [SuppressMessage("", "CA1001")] [BindableCustomPropertyProvider] [Service(ServiceLifetime.Scoped)] -internal sealed partial class CultivationViewModel : Abstraction.ViewModel +internal sealed partial class CultivationViewModel : Abstraction.ViewModel, IRecipient { private readonly ExclusiveTokenProvider exclusiveTokenProvider = new(); @@ -132,6 +133,17 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel UpdateEntryCollectionAsync(Projects?.CurrentItem).SafeForget(); } + public async void Receive(CultivationProjectEntriesChangedMessage message) + { + await taskContext.SwitchToMainThreadAsync(); + if (Projects?.CurrentItem is null) + { + return; + } + + await UpdateEntryCollectionAsync(Projects.CurrentItem).ConfigureAwait(false); + } + [Command("AddProjectCommand")] private async Task AddProjectAsync() {