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