cultivation save mode

This commit is contained in:
DismissedLight
2024-07-16 14:48:21 +08:00
parent 04114fb170
commit 2c139a1ff6
35 changed files with 416 additions and 258 deletions

View File

@@ -1247,6 +1247,18 @@
<data name="ViewDialogCultivatePromotionDeltaTitle" xml:space="preserve">
<value>添加或更新到当前养成计划</value>
</data>
<data name="ViewDialogCultivationConsumptionSaveStrategyCreateNewEntry" xml:space="preserve">
<value>总是创建新的养成目标物品</value>
</data>
<data name="ViewDialogCultivationConsumptionSaveStrategyHeader" xml:space="preserve">
<value>保存方式</value>
</data>
<data name="ViewDialogCultivationConsumptionSaveStrategyOverwriteExisting" xml:space="preserve">
<value>覆盖存在的养成目标物品</value>
</data>
<data name="ViewDialogCultivationConsumptionSaveStrategyPreserveExisting" xml:space="preserve">
<value>保留存在的养成目标物品</value>
</data>
<data name="ViewDialogDailyNoteNotificationDailyTaskNotify" xml:space="preserve">
<value>每日委托上线提醒</value>
</data>
@@ -1598,6 +1610,15 @@
<data name="ViewModelCultivationBatchAddIncompletedFormat" xml:space="preserve">
<value>操作未全部完成:添加 / 更新:{0} 个,跳过 {1} 个</value>
</data>
<data name="ViewModelCultivationConsumptionSaveNoItemHint" xml:space="preserve">
<value>选定的等级不需要养成材料</value>
</data>
<data name="ViewModelCultivationConsumptionSaveNoProjectHint" xml:space="preserve">
<value>尚未创建并选择养成计划</value>
</data>
<data name="ViewModelCultivationConsumptionSaveSkippedHint" xml:space="preserve">
<value>已存在该物品的养成项目</value>
</data>
<data name="ViewModelCultivationEntryAddSuccess" xml:space="preserve">
<value>已成功添加至当前养成计划</value>
</data>

View File

@@ -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

View File

@@ -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(),
};
}
}

View File

@@ -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,
}

View File

@@ -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,
}

View File

@@ -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
}
/// <inheritdoc/>
public async ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<CalculateItem> items, LevelInformation levelInformation)
public async ValueTask<ConsumptionSaveResultKind> 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<CultivateItem> toAdd = items.Select(item => CultivateItem.From(entryId, item));
IEnumerable<CultivateItem> toAdd = inputConsumption.Items.Select(item => CultivateItem.From(entryId, item));
cultivationDbService.AddCultivateItemRange(toAdd);
return true;
return ConsumptionSaveResultKind.Added;
}
/// <inheritdoc/>

View File

@@ -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;
/// <summary>
/// 养成计算服务
/// </summary>
[HighQuality]
internal interface ICultivationService
{
/// <summary>
/// 获取用于绑定的项目集合
/// </summary>
AdvancedDbCollectionView<CultivateProject> Projects { get; }
ValueTask<ObservableCollection<CultivateEntryView>> GetCultivateEntriesAsync(CultivateProject cultivateProject, ICultivationMetadataContext context);
@@ -26,32 +17,13 @@ internal interface ICultivationService
ValueTask<ObservableCollection<StatisticsCultivateItem>> GetStatisticsCultivateItemCollectionAsync(
CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token);
/// <summary>
/// 删除养成清单
/// </summary>
/// <param name="entryId">入口Id</param>
/// <returns>任务</returns>
ValueTask RemoveCultivateEntryAsync(Guid entryId);
/// <summary>
/// 异步移除项目
/// </summary>
/// <param name="project">项目</param>
/// <returns>任务</returns>
ValueTask RemoveProjectAsync(CultivateProject project);
ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Item> items, LevelInformation levelInformation);
ValueTask<ConsumptionSaveResultKind> SaveConsumptionAsync(InputConsumption inputConsumption);
/// <summary>
/// 保存养成物品状态
/// </summary>
/// <param name="item">养成物品</param>
void SaveCultivateItem(CultivateItemView item);
/// <summary>
/// 异步尝试添加新的项目
/// </summary>
/// <param name="project">项目</param>
/// <returns>添加操作的结果</returns>
ValueTask<ProjectAddResultKind> TryAddProjectAsync(CultivateProject project);
}

View File

@@ -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<CalculateItem> Items { get; init; }
public required LevelInformation LevelInformation { get; init; }
public required ConsumptionSaveStrategyKind Strategy { get; init; }
}

View File

@@ -203,7 +203,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
AsyncBarrier barrier = new(4);
List<HistoryWish> 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(),

View File

@@ -21,9 +21,15 @@ internal sealed partial class GameAccountService : IGameAccountService
private ObservableReorderableDbCollection<GameAccount>? gameAccounts;
public ObservableReorderableDbCollection<GameAccount> GameAccountCollection
public async ValueTask<ObservableReorderableDbCollection<GameAccount>> GetGameAccountCollectionAsync()
{
get => gameAccounts ??= gameDbService.GetGameAccountCollection();
if (gameAccounts is null)
{
await taskContext.SwitchToBackgroundAsync();
gameAccounts = gameDbService.GetGameAccountCollection();
}
return gameAccounts;
}
public async ValueTask<GameAccount?> DetectGameAccountAsync(SchemeType schemeType)

View File

@@ -9,14 +9,14 @@ namespace Snap.Hutao.Service.Game.Account;
internal interface IGameAccountService
{
ObservableReorderableDbCollection<GameAccount> GameAccountCollection { get; }
ValueTask AttachGameAccountToUidAsync(GameAccount gameAccount, string uid);
GameAccount? DetectCurrentGameAccount(SchemeType schemeType);
ValueTask<GameAccount?> DetectGameAccountAsync(SchemeType schemeType);
ValueTask<ObservableReorderableDbCollection<GameAccount>> GetGameAccountCollectionAsync();
ValueTask ModifyGameAccountAsync(GameAccount gameAccount);
ValueTask RemoveGameAccountAsync(GameAccount gameAccount);

View File

@@ -11,10 +11,6 @@ using Snap.Hutao.Service.Game.PathAbstraction;
namespace Snap.Hutao.Service.Game;
/// <summary>
/// 游戏服务
/// </summary>
[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;
/// <inheritdoc/>
public ObservableReorderableDbCollection<GameAccount> GameAccountCollection
public ValueTask<ObservableReorderableDbCollection<GameAccount>> GetGameAccountCollectionAsync()
{
get => gameAccountService.GameAccountCollection;
return gameAccountService.GetGameAccountCollectionAsync();
}
/// <inheritdoc/>
public ValueTask<ValueResult<bool, string>> GetGamePathAsync()
{
return gamePathService.SilentGetGamePathAsync();
}
/// <inheritdoc/>
public ChannelOptions GetChannelOptions()
{
return gameChannelOptionsService.GetChannelOptions();
}
/// <inheritdoc/>
public ValueTask<GameAccount?> DetectGameAccountAsync(SchemeType scheme)
{
return gameAccountService.DetectGameAccountAsync(scheme);
}
/// <inheritdoc/>
public GameAccount? DetectCurrentGameAccount(SchemeType scheme)
{
return gameAccountService.DetectCurrentGameAccount(scheme);
}
/// <inheritdoc/>
public ValueTask AttachGameAccountToUidAsync(GameAccount gameAccount, string uid)
{
return gameAccountService.AttachGameAccountToUidAsync(gameAccount, uid);
}
/// <inheritdoc/>
public ValueTask ModifyGameAccountAsync(GameAccount gameAccount)
{
return gameAccountService.ModifyGameAccountAsync(gameAccount);
}
/// <inheritdoc/>
public ValueTask RemoveGameAccountAsync(GameAccount gameAccount)
{
return gameAccountService.RemoveGameAccountAsync(gameAccount);
}
/// <inheritdoc/>
public bool IsGameRunning()
{
return LaunchExecutionEnsureGameNotRunningHandler.IsGameRunning(out _);

View File

@@ -10,8 +10,6 @@ namespace Snap.Hutao.Service.Game;
internal interface IGameServiceFacade
{
ObservableReorderableDbCollection<GameAccount> GameAccountCollection { get; }
ValueTask AttachGameAccountToUidAsync(GameAccount gameAccount, string uid);
ValueTask<GameAccount?> DetectGameAccountAsync(SchemeType scheme);
@@ -27,4 +25,6 @@ internal interface IGameServiceFacade
ValueTask RemoveGameAccountAsync(GameAccount gameAccount);
GameAccount? DetectCurrentGameAccount(SchemeType scheme);
ValueTask<ObservableReorderableDbCollection<GameAccount>> GetGameAccountCollectionAsync();
}

View File

@@ -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

View File

@@ -100,7 +100,11 @@ internal sealed class UniformStaggeredLayoutState
internal void Clear()
{
RecycleElements();
if (items.Count > 0)
{
RecycleElements();
}
ClearColumns();
ClearItems();
}

View File

@@ -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;

View File

@@ -60,7 +60,7 @@
</cwc:HeaderedContentControl>
</cwc:UniformGrid>
<RadioButtons
Name="ImportModeSelector"
x:Name="ImportModeSelector"
Grid.Row="1"
Margin="0,16,0,0"
Header="{shuxm:ResourceString Name=ViewDialogAchievementArchiveImportStrategy}"

View File

@@ -8,20 +8,11 @@ using Snap.Hutao.Service.Achievement;
namespace Snap.Hutao.UI.Xaml.View.Dialog;
/// <summary>
/// 成就对话框
/// </summary>
[HighQuality]
[DependencyProperty("UIAF", typeof(UIAF))]
internal sealed partial class AchievementImportDialog : ContentDialog
{
private readonly ITaskContext taskContext;
/// <summary>
/// 构造一个新的成就对话框
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="uiaf">uiaf数据</param>
public AchievementImportDialog(IServiceProvider serviceProvider, UIAF uiaf)
{
InitializeComponent();
@@ -30,10 +21,6 @@ internal sealed partial class AchievementImportDialog : ContentDialog
UIAF = uiaf;
}
/// <summary>
/// 异步获取导入选项
/// </summary>
/// <returns>导入选项</returns>
public async ValueTask<ValueResult<bool, ImportStrategyKind>> GetImportStrategyAsync()
{
await taskContext.SwitchToMainThreadAsync();

View File

@@ -73,5 +73,14 @@
SpinButtonPlacementMode="Inline"
Value="{x:Bind PromotionDelta.Weapon.LevelTarget, Mode=TwoWay}"/>
</cwc:SettingsCard>
<RadioButtons
x:Name="SaveModeSelector"
Header="{shuxm:ResourceString Name=ViewDialogCultivationConsumptionSaveStrategyHeader}"
SelectedIndex="0">
<RadioButton Content="{shuxm:ResourceString Name=ViewDialogCultivationConsumptionSaveStrategyPreserveExisting}"/>
<RadioButton Content="{shuxm:ResourceString Name=ViewDialogCultivationConsumptionSaveStrategyOverwriteExisting}"/>
<RadioButton Content="{shuxm:ResourceString Name=ViewDialogCultivationConsumptionSaveStrategyCreateNewEntry}"/>
</RadioButtons>
</StackPanel>
</ContentDialog>

View File

@@ -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<ValueResult<bool, AvatarPromotionDelta>> GetPromotionDeltaBaselineAsync()
public async ValueTask<ValueResult<bool, CultivatePromotionDeltaOptions>> 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));
}
}
}

View File

@@ -64,16 +64,14 @@
</Border>
</DataTemplate>
</ContentDialog.Resources>
<Grid>
<Grid Margin="0,8,0,0" RowSpacing="6">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="0,8,0,0"
Visibility="{x:Bind Avatar, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<StackPanel Grid.Row="0" Visibility="{x:Bind Avatar, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Grid
Padding="8"
DataContext="{x:Bind Avatar}"
@@ -123,10 +121,7 @@
<ItemsControl ItemTemplate="{StaticResource SkillTemplate}" ItemsSource="{x:Bind Avatar.Skills}"/>
</StackPanel>
<StackPanel
Grid.Row="1"
Margin="0,6,0,0"
Visibility="{x:Bind Weapon, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<StackPanel Grid.Row="1" Visibility="{x:Bind Weapon, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Grid
Padding="8"
DataContext="{x:Bind Weapon}"
@@ -174,5 +169,14 @@
</Grid>
</StackPanel>
<RadioButtons
x:Name="SaveModeSelector"
Grid.Row="2"
Header="{shuxm:ResourceString Name=ViewDialogCultivationConsumptionSaveStrategyHeader}"
SelectedIndex="0">
<RadioButton Content="{shuxm:ResourceString Name=ViewDialogCultivationConsumptionSaveStrategyPreserveExisting}"/>
<RadioButton Content="{shuxm:ResourceString Name=ViewDialogCultivationConsumptionSaveStrategyOverwriteExisting}"/>
<RadioButton Content="{shuxm:ResourceString Name=ViewDialogCultivationConsumptionSaveStrategyCreateNewEntry}"/>
</RadioButtons>
</Grid>
</ContentDialog>
</ContentDialog>

View File

@@ -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;
/// <summary>
/// 养成计算对话框
/// </summary>
[HighQuality]
[DependencyProperty("Avatar", typeof(ICalculableAvatar))]
[DependencyProperty("Weapon", typeof(ICalculableWeapon))]
internal sealed partial class CultivatePromotionDeltaDialog : ContentDialog
{
private readonly ITaskContext taskContext;
/// <summary>
/// 构造一个新的养成计算对话框
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="options">选项</param>
public CultivatePromotionDeltaDialog(IServiceProvider serviceProvider, CalculableOptions options)
{
InitializeComponent();
@@ -33,11 +25,7 @@ internal sealed partial class CultivatePromotionDeltaDialog : ContentDialog
Weapon = options.Weapon;
}
/// <summary>
/// 异步获取提升差异
/// </summary>
/// <returns>提升差异</returns>
public async ValueTask<ValueResult<bool, AvatarPromotionDelta>> GetPromotionDeltaAsync()
public async ValueTask<ValueResult<bool, CultivatePromotionDeltaOptions>> 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));
}
}
}

View File

@@ -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; }
}

View File

@@ -107,13 +107,15 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
/// <inheritdoc/>
public async ValueTask<bool> 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<AchievementServiceMetadataContext>(CancellationToken)
.ConfigureAwait(false);
if (!TryGetAchievements(archive, context, out List<AchievementView>? combined))
if (!TryGetAchievements(archive, context, out AdvancedCollectionView<AchievementView>? combined))
{
return;
}
AdvancedCollectionView<AchievementView> 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<AchievementView>? combined)
private bool TryGetAchievements(EntityArchive archive, AchievementServiceMetadataContext context, [NotNullWhen(true)] out AdvancedCollectionView<AchievementView>? 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);
}
}

View File

@@ -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<UserAndUidChangedMessage>
{
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<bool> 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<RefreshResultKind, Summary?> 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<CultivatePromotionDeltaDialog>(options).ConfigureAwait(false);
(bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
CultivatePromotionDeltaDialog dialog = await scopeContext.ContentDialogFactory
.CreateInstanceAsync<CultivatePromotionDeltaDialog>(options).ConfigureAwait(false);
(bool isOk, CultivatePromotionDeltaOptions deltaOptions) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
if (!isOk)
{
return;
}
Response<CalculatorBatchConsumption> response = await calculatorClient.BatchComputeAsync(userAndUid, delta).ConfigureAwait(false);
Response<CalculatorBatchConsumption> response;
using (IServiceScope scope = scopeContext.ServiceScopeFactory.CreateScope())
{
CalculateClient calculatorClient = scope.ServiceProvider.GetRequiredService<CalculateClient>();
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<CultivatePromotionDeltaBatchDialog>().ConfigureAwait(false);
(bool isOk, CalculatorAvatarPromotionDelta baseline) = await dialog.GetPromotionDeltaBaselineAsync().ConfigureAwait(false);
CultivatePromotionDeltaBatchDialog dialog = await scopeContext.ContentDialogFactory
.CreateInstanceAsync<CultivatePromotionDeltaBatchDialog>().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<CalculatorAvatarPromotionDelta> 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<CalculatorBatchConsumption> response = await calculatorClient.BatchComputeAsync(userAndUid, deltas).ConfigureAwait(false);
Response<CalculatorBatchConsumption> response;
using (IServiceScope scope = scopeContext.ServiceScopeFactory.CreateScope())
{
CalculateClient calculatorClient = scope.ServiceProvider.GetRequiredService<CalculateClient>();
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<bool> SaveCultivationAsync(CalculatorConsumption consumption, CalculatorAvatarPromotionDelta delta)
/// <returns><see langword="true"/> if we can continue saving consumptions, otherwise <see langword="false"/>.</returns>
private async ValueTask<bool> SaveCultivationAsync(CalculatorConsumption consumption, CultivatePromotionDeltaOptions options)
{
LevelInformation levelInformation = LevelInformation.From(delta);
LevelInformation levelInformation = LevelInformation.From(options.Delta);
List<CalculatorItem> 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);
}
}
}

View File

@@ -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; }
}

View File

@@ -7,5 +7,4 @@ internal struct BatchCultivateResult
{
public int SucceedCount;
public int SkippedCount;
public bool Interrupted;
}

View File

@@ -325,7 +325,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
async ValueTask UpdateGameAccountsViewAsync()
{
gameAccountFilter = new(SelectedScheme?.GetSchemeType());
ObservableReorderableDbCollection<GameAccount> accounts = gameService.GameAccountCollection;
ObservableReorderableDbCollection<GameAccount> accounts = await gameService.GetGameAccountCollectionAsync().ConfigureAwait(false);
AdvancedCollectionView<GameAccount> accountsView = new(accounts) { Filter = gameAccountFilter.Filter };
await taskContext.SwitchToMainThreadAsync();

View File

@@ -43,13 +43,14 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
protected override async Task LoadAsync()
{
LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile();
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
ObservableCollection<GameAccount> 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);
}
}

View File

@@ -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;

View File

@@ -85,7 +85,7 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
.ConfigureAwait(false);
}
AdvancedCollectionView<SpiralAbyssView> spiralAbyssEntries = new(collection);
AdvancedCollectionView<SpiralAbyssView> spiralAbyssEntries = collection.ToAdvancedCollectionView();
await taskContext.SwitchToMainThreadAsync();
SpiralAbyssEntries = spiralAbyssEntries;

View File

@@ -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<Avatar>? avatars;
@@ -109,7 +108,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
{
AdvancedCollectionView<Avatar> avatarsView = new(list);
AdvancedCollectionView<Avatar> 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<CultivatePromotionDeltaDialog>(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<CalculateBatchConsumption> response = await calculateClient
.BatchComputeAsync(userAndUid, delta)
.ConfigureAwait(false);
Response<CalculateBatchConsumption> response;
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
CalculateClient calculateClient = scope.ServiceProvider.GetRequiredService<CalculateClient>();
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)

View File

@@ -68,7 +68,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
{
AdvancedCollectionView<Monster> monstersView = new(ordered);
AdvancedCollectionView<Monster> monstersView = ordered.ToAdvancedCollectionView();
await taskContext.SwitchToMainThreadAsync();
Monsters = monstersView;

View File

@@ -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<Weapon> weaponsView = new(list);
AdvancedCollectionView<Weapon> 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<CultivatePromotionDeltaDialog>(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<CalculateBatchConsumption> response = await calculateClient
.BatchComputeAsync(userAndUid, delta)
.ConfigureAwait(false);
Response<CalculateBatchConsumption> response;
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
CalculateClient calculateClient = scope.ServiceProvider.GetRequiredService<CalculateClient>();
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)