refactor wiki viewmodel

This commit is contained in:
Lightczx
2023-08-10 17:25:37 +08:00
parent 420bf6ff41
commit d9169df3b8
18 changed files with 536 additions and 490 deletions

View File

@@ -1,10 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.Wiki;
namespace Snap.Hutao.Model.Metadata.Avatar; namespace Snap.Hutao.Model.Metadata.Avatar;
@@ -13,4 +10,13 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
/// </summary> /// </summary>
internal sealed class AvatarBaseValue : BaseValue internal sealed class AvatarBaseValue : BaseValue
{ {
public override float GetValue(FightProperty fightProperty)
{
return fightProperty switch
{
FightProperty.FIGHT_PROP_CRITICAL => 0.05F,
FightProperty.FIGHT_PROP_CRITICAL_HURT => 0.5F,
_ => base.GetValue(fightProperty),
};
}
} }

View File

@@ -12,6 +12,8 @@ namespace Snap.Hutao.Model.Metadata.Monster;
/// </summary> /// </summary>
internal sealed class Monster internal sealed class Monster
{ {
internal const uint MaxLevel = 100;
/// <summary> /// <summary>
/// Id /// Id
/// </summary> /// </summary>

View File

@@ -29,11 +29,9 @@ internal sealed class Promote
/// </summary> /// </summary>
public List<TypeValue<FightProperty, float>> AddProperties { get; set; } = default!; public List<TypeValue<FightProperty, float>> AddProperties { get; set; } = default!;
/// <summary> public float GetValue(FightProperty property)
/// 属性映射
/// </summary>
public Dictionary<FightProperty, float> AddPropertyMap
{ {
get => addPropertyMap ??= AddProperties.ToDictionary(a => a.Type, a => a.Value); addPropertyMap ??= AddProperties.ToDictionary(a => a.Type, a => a.Value);
return addPropertyMap.GetValueOrDefault(property);
} }
} }

View File

@@ -0,0 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Model.Primitive;
internal static class LevelFormat
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Format(uint value)
{
return $"Lv.{value}";
}
}

View File

@@ -45,10 +45,10 @@ internal sealed partial class HutaoCache : IHutaoCache
public Overview? Overview { get; set; } public Overview? Overview { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public List<AvatarCollocationView>? AvatarCollocations { get; set; } public Dictionary<AvatarId, AvatarCollocationView>? AvatarCollocations { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public List<WeaponCollocationView>? WeaponCollocations { get; set; } public Dictionary<WeaponId, WeaponCollocationView>? WeaponCollocations { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask<bool> InitializeForDatabaseViewModelAsync() public async ValueTask<bool> InitializeForDatabaseViewModelAsync()
@@ -153,7 +153,7 @@ internal sealed partial class HutaoCache : IHutaoCache
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)), Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
Weapons = co.Weapons.SelectList(w => new WeaponView(idWeaponMap[w.Item], w.Rate)), Weapons = co.Weapons.SelectList(w => new WeaponView(idWeaponMap[w.Item], w.Rate)),
ReliquarySets = co.Reliquaries.SelectList(r => new ReliquarySetView(r, idReliquarySetMap)), ReliquarySets = co.Reliquaries.SelectList(r => new ReliquarySetView(r, idReliquarySetMap)),
}); }).ToDictionary(a => a.AvatarId);
} }
private async ValueTask WeaponCollocationsAsync(Dictionary<AvatarId, Avatar> idAvatarMap) private async ValueTask WeaponCollocationsAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
@@ -169,7 +169,7 @@ internal sealed partial class HutaoCache : IHutaoCache
{ {
WeaponId = co.WeaponId, WeaponId = co.WeaponId,
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)), Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
}); }).ToDictionary(w => w.WeaponId);
} }
[SuppressMessage("", "SH003")] [SuppressMessage("", "SH003")]

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Binding.Hutao; using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.Complex; using Snap.Hutao.ViewModel.Complex;
using Snap.Hutao.Web.Hutao.Model; using Snap.Hutao.Web.Hutao.Model;
@@ -41,12 +42,12 @@ internal interface IHutaoCache
/// <summary> /// <summary>
/// 角色搭配 /// 角色搭配
/// </summary> /// </summary>
List<AvatarCollocationView>? AvatarCollocations { get; set; } Dictionary<AvatarId, AvatarCollocationView>? AvatarCollocations { get; set; }
/// <summary> /// <summary>
/// 武器搭配 /// 武器搭配
/// </summary> /// </summary>
List<WeaponCollocationView>? WeaponCollocations { get; set; } Dictionary<WeaponId, WeaponCollocationView>? WeaponCollocations { get; set; }
/// <summary> /// <summary>
/// 为数据库视图模型初始化 /// 为数据库视图模型初始化

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Service.User;
internal interface IUserInitializationService
{
ValueTask<ViewModel.User.User?> CreateOrDefaultUserFromCookieAsync(Cookie cookie, bool isOversea, CancellationToken token = default(CancellationToken));
ValueTask<ViewModel.User.User> ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default(CancellationToken));
}

View File

@@ -0,0 +1,185 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.Passport;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Response;
namespace Snap.Hutao.Service.User;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IUserInitializationService))]
internal sealed partial class UserInitializationService : IUserInitializationService
{
private readonly IServiceProvider serviceProvider;
public async ValueTask<ViewModel.User.User> ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default)
{
ViewModel.User.User user = ViewModel.User.User.From(inner, serviceProvider);
if (!await InitializeUserAsync(user, token).ConfigureAwait(false))
{
user.UserInfo = new() { Nickname = SH.ModelBindingUserInitializationFailed };
user.UserGameRoles = new();
}
return user;
}
public async ValueTask<ViewModel.User.User?> CreateOrDefaultUserFromCookieAsync(Cookie cookie, bool isOversea, CancellationToken token = default)
{
// 这里只负责创建实体用户,稍后在用户服务中保存到数据库
Model.Entity.User entity = Model.Entity.User.From(cookie, isOversea);
entity.Aid = cookie.GetValueOrDefault(Cookie.STUID);
entity.Mid = isOversea ? entity.Aid : cookie.GetValueOrDefault(Cookie.MID);
entity.IsOversea = isOversea;
if (entity.Aid is not null && entity.Mid is not null)
{
ViewModel.User.User user = ViewModel.User.User.From(entity, serviceProvider);
bool initialized = await InitializeUserAsync(user, token).ConfigureAwait(false);
return initialized ? user : null;
}
else
{
return null;
}
}
private async ValueTask<bool> InitializeUserAsync(ViewModel.User.User user, CancellationToken token = default)
{
if (user.IsInitialized)
{
// Prevent multiple initialization.
return true;
}
if (user.SToken is null)
{
return false;
}
bool isOversea = user.Entity.IsOversea;
if (!await TrySetUserLTokenAsync(user, token).ConfigureAwait(false))
{
return false;
}
if (!await TrySetUserCookieTokenAsync(user, token).ConfigureAwait(false))
{
return false;
}
if (!await TrySetUserUserInfoAsync(user, token).ConfigureAwait(false))
{
return false;
}
if (!await TrySetUserUserGameRolesAsync(user, token).ConfigureAwait(false))
{
return false;
}
user.SelectedUserGameRole = user.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
return user.IsInitialized = true;
}
private async ValueTask<bool> TrySetUserLTokenAsync(ViewModel.User.User user, CancellationToken token)
{
if (user.LToken is not null)
{
return true;
}
Response<LTokenWrapper> lTokenResponse = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.IsOversea)
.GetLTokenBySTokenAsync(user.Entity, token)
.ConfigureAwait(false);
if (lTokenResponse.IsOk())
{
user.LToken = new()
{
[Cookie.LTUID] = user.Entity.Aid ?? string.Empty,
[Cookie.LTOKEN] = lTokenResponse.Data.LToken,
};
return true;
}
else
{
return false;
}
}
private async ValueTask<bool> TrySetUserCookieTokenAsync(ViewModel.User.User user, CancellationToken token)
{
if (user.CookieToken is not null)
{
return true;
}
Response<UidCookieToken> cookieTokenResponse = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.IsOversea)
.GetCookieAccountInfoBySTokenAsync(user.Entity, token)
.ConfigureAwait(false);
if (cookieTokenResponse.IsOk())
{
user.CookieToken = new()
{
[Cookie.ACCOUNT_ID] = user.Entity.Aid ?? string.Empty,
[Cookie.COOKIE_TOKEN] = cookieTokenResponse.Data.CookieToken,
};
return true;
}
else
{
return false;
}
}
private async ValueTask<bool> TrySetUserUserInfoAsync(ViewModel.User.User user, CancellationToken token)
{
Response<UserFullInfoWrapper> response = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IUserClient>>()
.Create(user.IsOversea)
.GetUserFullInfoAsync(user.Entity, token)
.ConfigureAwait(false);
if (response.IsOk())
{
user.UserInfo = response.Data.UserInfo;
return true;
}
else
{
return false;
}
}
private async ValueTask<bool> TrySetUserUserGameRolesAsync(ViewModel.User.User user, CancellationToken token)
{
Response<ListWrapper<UserGameRole>> userGameRolesResponse = await serviceProvider
.GetRequiredService<BindingClient>()
.GetUserGameRolesOverseaAwareAsync(user.Entity, token)
.ConfigureAwait(false);
if (userGameRolesResponse.IsOk())
{
user.UserGameRoles = userGameRolesResponse.Data.List;
return user.UserGameRoles.Any();
}
else
{
return false;
}
}
}

View File

@@ -25,11 +25,12 @@ namespace Snap.Hutao.Service.User;
[Injection(InjectAs.Singleton, typeof(IUserService))] [Injection(InjectAs.Singleton, typeof(IUserService))]
internal sealed partial class UserService : IUserService internal sealed partial class UserService : IUserService
{ {
private readonly ITaskContext taskContext;
private readonly IUserDbService userDbService;
private readonly IServiceProvider serviceProvider;
private readonly IMessenger messenger;
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent; private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
private readonly IUserInitializationService userInitializationService;
private readonly IServiceProvider serviceProvider;
private readonly IUserDbService userDbService;
private readonly ITaskContext taskContext;
private readonly IMessenger messenger;
private ObservableCollection<BindingUser>? userCollection; private ObservableCollection<BindingUser>? userCollection;
private ObservableCollection<UserAndUid>? userAndUidCollection; private ObservableCollection<UserAndUid>? userAndUidCollection;
@@ -63,7 +64,7 @@ internal sealed partial class UserService : IUserService
if (userCollection is null) if (userCollection is null)
{ {
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false); List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
List<BindingUser> users = await entities.SelectListAsync(BindingUser.ResumeAsync, default).ConfigureAwait(false); List<BindingUser> users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync, default).ConfigureAwait(false);
userCollection = users.ToObservableCollection(); userCollection = users.ToObservableCollection();
try try
@@ -72,7 +73,7 @@ internal sealed partial class UserService : IUserService
} }
catch (InvalidOperationException ex) catch (InvalidOperationException ex)
{ {
throw new UserdataCorruptedException(SH.ServiceUserCurrentMultiMatched, ex); ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex);
} }
} }
@@ -184,7 +185,7 @@ internal sealed partial class UserService : IUserService
private async ValueTask<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(Cookie cookie, bool isOversea) private async ValueTask<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(Cookie cookie, bool isOversea)
{ {
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
BindingUser? newUser = await BindingUser.CreateAsync(cookie, isOversea).ConfigureAwait(false); BindingUser? newUser = await userInitializationService.CreateOrDefaultUserFromCookieAsync(cookie, isOversea).ConfigureAwait(false);
if (newUser is not null) if (newUser is not null)
{ {

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Model; using Snap.Hutao.Model;
@@ -17,34 +18,36 @@ namespace Snap.Hutao.ViewModel.User;
/// <summary> /// <summary>
/// 用于视图绑定的用户 /// 用于视图绑定的用户
/// TODO: move initializaion part to service
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, ISelectable internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, IMappingFrom<User, EntityUser, IServiceProvider>, ISelectable
{ {
private readonly EntityUser inner; private readonly EntityUser inner;
private readonly IServiceProvider serviceProvider;
private UserGameRole? selectedUserGameRole; private UserGameRole? selectedUserGameRole;
private bool isInitialized;
/// <summary> /// <summary>
/// 构造一个新的绑定视图用户 /// 构造一个新的绑定视图用户
/// </summary> /// </summary>
/// <param name="user">用户实体</param> /// <param name="user">用户实体</param>
private User(EntityUser user) private User(EntityUser user, IServiceProvider serviceProvider)
{ {
inner = user; inner = user;
this.serviceProvider = serviceProvider;
} }
/// <summary> public bool IsInitialized { get; set; }
/// 用户信息
/// </summary>
public UserInfo? UserInfo { get; private set; }
/// <summary> /// <summary>
/// 用户信息 /// 用户信息
/// </summary> /// </summary>
public List<UserGameRole> UserGameRoles { get; private set; } = default!; public UserInfo? UserInfo { get; set; }
/// <summary>
/// 用户信息
/// </summary>
public List<UserGameRole> UserGameRoles { get; set; } = default!;
/// <summary> /// <summary>
/// 用户信息, 请勿访问set /// 用户信息, 请勿访问set
@@ -56,9 +59,8 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, ISelecta
{ {
if (SetProperty(ref selectedUserGameRole, value)) if (SetProperty(ref selectedUserGameRole, value))
{ {
Ioc.Default IMessenger messenger = serviceProvider.GetRequiredService<IMessenger>();
.GetRequiredService<IMessenger>() messenger.Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this });
.Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this });
} }
} }
} }
@@ -103,178 +105,8 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, ISelecta
/// </summary> /// </summary>
public EntityUser Entity { get => inner; } public EntityUser Entity { get => inner; }
/// <summary> public static User From(EntityUser user, IServiceProvider provider)
/// 从数据库恢复用户
/// </summary>
/// <param name="inner">数据库实体</param>
/// <param name="token">取消令牌</param>
/// <returns>用户</returns>
internal static async ValueTask<User> ResumeAsync(EntityUser inner, CancellationToken token = default)
{ {
User user = new(inner); return new(user, provider);
if (!await user.InitializeCoreAsync(token).ConfigureAwait(false))
{
user.UserInfo = new() { Nickname = SH.ModelBindingUserInitializationFailed };
user.UserGameRoles = new();
}
return user;
}
/// <summary>
/// 创建并初始化用户
/// </summary>
/// <param name="cookie">cookie</param>
/// <param name="isOversea">是否为国际服</param>
/// <param name="token">取消令牌</param>
/// <returns>用户</returns>
internal static async Task<User?> CreateAsync(Cookie cookie, bool isOversea, CancellationToken token = default)
{
// 这里只负责创建实体用户,稍后在用户服务中保存到数据库
EntityUser entity = EntityUser.From(cookie, isOversea);
entity.Aid = cookie.GetValueOrDefault(Cookie.STUID);
entity.Mid = isOversea ? entity.Aid : cookie.GetValueOrDefault(Cookie.MID);
entity.IsOversea = isOversea;
if (entity.Aid != null && entity.Mid != null)
{
User user = new(entity);
bool initialized = await user.InitializeCoreAsync(token).ConfigureAwait(false);
return initialized ? user : null;
}
else
{
return null;
}
}
private async Task<bool> InitializeCoreAsync(CancellationToken token = default)
{
if (isInitialized)
{
// Prevent multiple initialization.
return true;
}
if (SToken == null)
{
return false;
}
using (IServiceScope scope = Ioc.Default.CreateScope())
{
bool isOversea = Entity.IsOversea;
if (!await TrySetLTokenAsync(scope.ServiceProvider, token).ConfigureAwait(false))
{
return false;
}
if (!await TrySetCookieTokenAsync(scope.ServiceProvider, token).ConfigureAwait(false))
{
return false;
}
if (!await TrySetUserInfoAsync(scope.ServiceProvider, token).ConfigureAwait(false))
{
return false;
}
if (!await TrySetUserGameRolesAsync(scope.ServiceProvider, token).ConfigureAwait(false))
{
return false;
}
}
SelectedUserGameRole = UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
return isInitialized = true;
}
private async Task<bool> TrySetLTokenAsync(IServiceProvider provider, CancellationToken token)
{
if (LToken != null)
{
return true;
}
Response<LTokenWrapper> lTokenResponse = await provider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(Entity.IsOversea)
.GetLTokenBySTokenAsync(Entity, token)
.ConfigureAwait(false);
if (lTokenResponse.IsOk())
{
LToken = Cookie.Parse($"{Cookie.LTUID}={Entity.Aid};{Cookie.LTOKEN}={lTokenResponse.Data.LToken}");
return true;
}
else
{
return false;
}
}
private async Task<bool> TrySetCookieTokenAsync(IServiceProvider provider, CancellationToken token)
{
if (CookieToken != null)
{
return true;
}
Response<UidCookieToken> cookieTokenResponse = await provider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(Entity.IsOversea)
.GetCookieAccountInfoBySTokenAsync(Entity, token)
.ConfigureAwait(false);
if (cookieTokenResponse.IsOk())
{
CookieToken = Cookie.Parse($"{Cookie.ACCOUNT_ID}={Entity.Aid};{Cookie.COOKIE_TOKEN}={cookieTokenResponse.Data.CookieToken}");
return true;
}
else
{
return false;
}
}
private async Task<bool> TrySetUserInfoAsync(IServiceProvider provider, CancellationToken token)
{
Response<UserFullInfoWrapper> response = await provider
.GetRequiredService<IOverseaSupportFactory<IUserClient>>()
.Create(Entity.IsOversea)
.GetUserFullInfoAsync(Entity, token)
.ConfigureAwait(false);
if (response.IsOk())
{
UserInfo = response.Data.UserInfo;
return true;
}
else
{
return false;
}
}
private async Task<bool> TrySetUserGameRolesAsync(IServiceProvider provider, CancellationToken token)
{
Response<ListWrapper<UserGameRole>> userGameRolesResponse = await provider
.GetRequiredService<BindingClient>()
.GetUserGameRolesOverseaAwareAsync(Entity, token)
.ConfigureAwait(false);
if (userGameRolesResponse.IsOk())
{
UserGameRoles = userGameRolesResponse.Data.List;
return UserGameRoles.Any();
}
else
{
return false;
}
} }
} }

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core;
using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO.DataTransfer; using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Navigation;
@@ -23,9 +24,10 @@ namespace Snap.Hutao.ViewModel.User;
[Injection(InjectAs.Singleton)] [Injection(InjectAs.Singleton)]
internal sealed partial class UserViewModel : ObservableObject internal sealed partial class UserViewModel : ObservableObject
{ {
private readonly INavigationService navigationService;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly Core.RuntimeOptions hutaoOptions; private readonly RuntimeOptions runtimeOptions;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IUserService userService; private readonly IUserService userService;
@@ -44,7 +46,7 @@ internal sealed partial class UserViewModel : ObservableObject
{ {
userService.Current = value; userService.Current = value;
if (value != null) if (value is not null)
{ {
value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen); value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
} }
@@ -63,12 +65,13 @@ internal sealed partial class UserViewModel : ObservableObject
/// <param name="optionResult">操作结果</param> /// <param name="optionResult">操作结果</param>
/// <param name="uid">uid</param> /// <param name="uid">uid</param>
/// <returns>任务</returns> /// <returns>任务</returns>
public async Task HandleUserOptionResultAsync(UserOptionResult optionResult, string uid) internal async ValueTask HandleUserOptionResultAsync(UserOptionResult optionResult, string uid)
{ {
switch (optionResult) switch (optionResult)
{ {
case UserOptionResult.Added: case UserOptionResult.Added:
if (Users!.Count == 1) ArgumentNullException.ThrowIfNull(Users);
if (Users.Count == 1)
{ {
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
SelectedUser = Users.Single(); SelectedUser = Users.Single();
@@ -107,16 +110,16 @@ internal sealed partial class UserViewModel : ObservableObject
[Command("AddUserCommand")] [Command("AddUserCommand")]
private Task AddUserAsync() private Task AddUserAsync()
{ {
return AddUserCoreAsync(false); return AddUserCoreAsync(false).AsTask();
} }
[Command("AddOverseaUserCommand")] [Command("AddOverseaUserCommand")]
private Task AddOverseaUserAsync() private Task AddOverseaUserAsync()
{ {
return AddUserCoreAsync(true); return AddUserCoreAsync(true).AsTask();
} }
private async Task AddUserCoreAsync(bool isOversea) private async ValueTask AddUserCoreAsync(bool isOversea)
{ {
// ContentDialog must be created by main thread. // ContentDialog must be created by main thread.
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
@@ -126,9 +129,9 @@ internal sealed partial class UserViewModel : ObservableObject
ValueResult<bool, string> result = await dialog.GetInputCookieAsync().ConfigureAwait(false); ValueResult<bool, string> result = await dialog.GetInputCookieAsync().ConfigureAwait(false);
// User confirms the input // User confirms the input
if (result.IsOk) if (result.TryGetValue(out string rawCookie))
{ {
Cookie cookie = Cookie.Parse(result.Value); Cookie cookie = Cookie.Parse(rawCookie);
(UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(cookie, isOversea).ConfigureAwait(false); (UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(cookie, isOversea).ConfigureAwait(false);
@@ -139,11 +142,9 @@ internal sealed partial class UserViewModel : ObservableObject
[Command("LoginMihoyoUserCommand")] [Command("LoginMihoyoUserCommand")]
private void LoginMihoyoUser() private void LoginMihoyoUser()
{ {
if (hutaoOptions.IsWebView2Supported) if (runtimeOptions.IsWebView2Supported)
{ {
serviceProvider navigationService.Navigate<LoginMihoyoUserPage>(INavigationAwaiter.Default);
.GetRequiredService<INavigationService>()
.Navigate<LoginMihoyoUserPage>(INavigationAwaiter.Default);
} }
else else
{ {
@@ -154,11 +155,9 @@ internal sealed partial class UserViewModel : ObservableObject
[Command("LoginHoyoverseUserCommand")] [Command("LoginHoyoverseUserCommand")]
private void LoginHoyoverseUser() private void LoginHoyoverseUser()
{ {
if (hutaoOptions.IsWebView2Supported) if (runtimeOptions.IsWebView2Supported)
{ {
serviceProvider navigationService.Navigate<LoginHoyoverseUserPage>(INavigationAwaiter.Default);
.GetRequiredService<INavigationService>()
.Navigate<LoginHoyoverseUserPage>(INavigationAwaiter.Default);
} }
else else
{ {
@@ -169,11 +168,11 @@ internal sealed partial class UserViewModel : ObservableObject
[Command("RemoveUserCommand")] [Command("RemoveUserCommand")]
private async Task RemoveUserAsync(User? user) private async Task RemoveUserAsync(User? user)
{ {
if (user != null) if (user is not null)
{ {
try try
{ {
await userService.RemoveUserAsync(user!).ConfigureAwait(false); await userService.RemoveUserAsync(user).ConfigureAwait(false);
infoBarService.Success(string.Format(SH.ViewModelUserRemoved, user.UserInfo?.Nickname)); infoBarService.Success(string.Format(SH.ViewModelUserRemoved, user.UserInfo?.Nickname));
} }
catch (UserdataCorruptedException ex) catch (UserdataCorruptedException ex)
@@ -188,26 +187,29 @@ internal sealed partial class UserViewModel : ObservableObject
{ {
try try
{ {
ArgumentNullException.ThrowIfNull(user);
string cookieString = new StringBuilder() string cookieString = new StringBuilder()
.Append(user!.SToken) .Append(user.SToken)
.AppendIf(user.SToken != null, ';') .AppendIf(user.SToken is not null, ';')
.Append(user.LToken) .Append(user.LToken)
.AppendIf(user.LToken != null, ';') .AppendIf(user.LToken is not null, ';')
.Append(user.CookieToken) .Append(user.CookieToken)
.ToString(); .ToString();
serviceProvider.GetRequiredService<IClipboardInterop>().SetText(cookieString); serviceProvider.GetRequiredService<IClipboardInterop>().SetText(cookieString);
infoBarService.Success(string.Format(SH.ViewModelUserCookieCopied, user.UserInfo!.Nickname));
ArgumentNullException.ThrowIfNull(user.UserInfo);
infoBarService.Success(string.Format(SH.ViewModelUserCookieCopied, user.UserInfo.Nickname));
} }
catch (Exception e) catch (Exception ex)
{ {
infoBarService.Error(e); infoBarService.Error(ex);
} }
} }
[Command("RefreshCookieTokenCommand")] [Command("RefreshCookieTokenCommand")]
private async Task RefreshCookieTokenAsync() private async Task RefreshCookieTokenAsync()
{ {
if (SelectedUser != null) if (SelectedUser is not null)
{ {
if (await userService.RefreshCookieTokenAsync(SelectedUser).ConfigureAwait(false)) if (await userService.RefreshCookieTokenAsync(SelectedUser).ConfigureAwait(false))
{ {

View File

@@ -26,6 +26,7 @@ internal static class AvatarFilter
{ {
List<bool> matches = new(); List<bool> matches = new();
// TODO: use Collection Literals
foreach (StringSegment segment in new StringTokenizer(input, new char[] { ' ' })) foreach (StringSegment segment in new StringTokenizer(input, new char[] { ' ' }))
{ {
string value = segment.ToString(); string value = segment.ToString();

View File

@@ -71,7 +71,7 @@ internal sealed class BaseValueInfo : ObservableObject
/// </summary> /// </summary>
public string CurrentLevelFormatted public string CurrentLevelFormatted
{ {
get => $"Lv.{CurrentLevel}"; get => LevelFormat.Format(CurrentLevel);
} }
/// <summary> /// <summary>
@@ -95,10 +95,10 @@ internal sealed class BaseValueInfo : ObservableObject
Values = propValues.SelectList(propValue => Values = propValues.SelectList(propValue =>
{ {
float value = propValue.Value * growCurveMap[level].GetValueOrDefault(propValue.Type); float value = propValue.Value * growCurveMap[level].GetValueOrDefault(propValue.Type);
if (promoteMap != null) if (promoteMap is not null)
{ {
PromoteLevel promoteLevel = GetPromoteLevel(level, promoted); PromoteLevel promoteLevel = GetPromoteLevel(level, promoted);
float addValue = promoteMap[promoteLevel].AddPropertyMap.GetValueOrDefault(propValue.Property, 0F); float addValue = promoteMap[promoteLevel].GetValue(propValue.Property);
value += addValue; value += addValue;
} }
@@ -109,59 +109,36 @@ internal sealed class BaseValueInfo : ObservableObject
private PromoteLevel GetPromoteLevel(in Level level, bool promoted) private PromoteLevel GetPromoteLevel(in Level level, bool promoted)
{ {
if (MaxLevel <= 70) if (MaxLevel <= 70 && level.Value == 70U)
{ {
if (promoted) return 4;
}
if (promoted)
{
return level.Value switch
{ {
return level.Value switch >= 80U => 6,
{ >= 70U => 5,
>= 60U => 4, >= 60U => 4,
>= 50U => 3, >= 50U => 3,
>= 40U => 2, >= 40U => 2,
>= 20U => 1, >= 20U => 1,
_ => 0, _ => 0,
}; };
}
else
{
return level.Value switch
{
> 60U => 4,
> 50U => 3,
> 40U => 2,
> 20U => 1,
_ => 0,
};
}
} }
else else
{ {
if (promoted) return level.Value switch
{ {
return level.Value switch > 80U => 6,
{ > 70U => 5,
>= 80U => 6, > 60U => 4,
>= 70U => 5, > 50U => 3,
>= 60U => 4, > 40U => 2,
>= 50U => 3, > 20U => 1,
>= 40U => 2, _ => 0,
>= 20U => 1, };
_ => 0,
};
}
else
{
return level.Value switch
{
> 80U => 6,
> 70U => 5,
> 60U => 4,
> 50U => 3,
> 40U => 2,
> 20U => 1,
_ => 0,
};
}
} }
} }
} }

View File

@@ -31,7 +31,7 @@ internal sealed class CookBonusView
/// <returns>新的料理奖励视图</returns> /// <returns>新的料理奖励视图</returns>
public static CookBonusView? Create(CookBonus? cookBonus, Dictionary<MaterialId, Material> idMaterialMap) public static CookBonusView? Create(CookBonus? cookBonus, Dictionary<MaterialId, Material> idMaterialMap)
{ {
if (cookBonus == null) if (cookBonus is null)
{ {
return null; return null;
} }

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
namespace Snap.Hutao.ViewModel.Wiki; namespace Snap.Hutao.ViewModel.Wiki;
@@ -23,6 +24,11 @@ internal sealed class PropertyCurveValue
Value = value; Value = value;
} }
public PropertyCurveValue(FightProperty property, Dictionary<FightProperty, GrowCurveType> growCurve, BaseValue baseValue)
: this(property, growCurve.GetValueOrDefault(property), baseValue.GetValue(property))
{
}
/// <summary> /// <summary>
/// 战斗属性值 /// 战斗属性值
/// </summary> /// </summary>

View File

@@ -2,11 +2,9 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.WinUI.UI; using CommunityToolkit.WinUI.UI;
using Microsoft.Extensions.Primitives;
using Snap.Hutao.Model.Calculable; using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Intrinsic.Immutable;
using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item; using Snap.Hutao.Model.Metadata.Item;
@@ -17,15 +15,14 @@ using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User; using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog; using Snap.Hutao.View.Dialog;
using Snap.Hutao.ViewModel.Complex;
using Snap.Hutao.Web.Response; using Snap.Hutao.Web.Response;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption; using CalculateConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
using CalcItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item; using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
using CalcItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper; using CalculateItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
namespace Snap.Hutao.ViewModel.Wiki; namespace Snap.Hutao.ViewModel.Wiki;
@@ -41,6 +38,8 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
private readonly IMetadataService metadataService; private readonly IMetadataService metadataService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IHutaoCache hutaoCache; private readonly IHutaoCache hutaoCache;
private readonly IInfoBarService infoBarService;
private readonly IUserService userService;
private AdvancedCollectionView? avatars; private AdvancedCollectionView? avatars;
private Avatar? selected; private Avatar? selected;
@@ -78,150 +77,159 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
/// </summary> /// </summary>
public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); } public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); }
/// <inheritdoc/> protected override async ValueTask<bool> InitializeUIAsync()
protected override async Task OpenUIAsync()
{ {
if (await metadataService.InitializeAsync().ConfigureAwait(false)) if (!await metadataService.InitializeAsync().ConfigureAwait(false))
{ {
levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false); return false;
promotes = await metadataService.GetAvatarPromotesAsync().ConfigureAwait(false);
Dictionary<MaterialId, Material> idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
List<Avatar> avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false);
List<Avatar> sorted = avatars
.OrderByDescending(avatar => avatar.BeginTime)
.ThenByDescending(avatar => avatar.Sort)
.ToList();
await CombineComplexDataAsync(sorted, idMaterialMap).ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
Avatars = new AdvancedCollectionView(sorted, true);
Selected = Avatars.Cast<Avatar>().FirstOrDefault();
} }
levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false);
promotes = await metadataService.GetAvatarPromotesAsync().ConfigureAwait(false);
Dictionary<MaterialId, Material> idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
List<Avatar> avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false);
List<Avatar> sorted = avatars
.OrderByDescending(avatar => avatar.BeginTime)
.ThenByDescending(avatar => avatar.Sort)
.ToList();
await CombineComplexDataAsync(sorted, idMaterialMap).ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
Avatars = new AdvancedCollectionView(sorted, true);
Selected = Avatars.Cast<Avatar>().FirstOrDefault();
return true;
} }
private async Task CombineComplexDataAsync(List<Avatar> avatars, Dictionary<MaterialId, Material> idMaterialMap) private async ValueTask CombineComplexDataAsync(List<Avatar> avatars, Dictionary<MaterialId, Material> idMaterialMap)
{ {
if (await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false)) if (!await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false))
{ {
Dictionary<AvatarId, AvatarCollocationView> idCollocations = hutaoCache.AvatarCollocations!.ToDictionary(a => a.AvatarId); return;
}
foreach (Avatar avatar in avatars) ArgumentNullException.ThrowIfNull(hutaoCache.AvatarCollocations);
{
avatar.Collocation = idCollocations.GetValueOrDefault(avatar.Id); foreach (Avatar avatar in avatars)
avatar.CookBonusView ??= CookBonusView.Create(avatar.FetterInfo.CookBonus, idMaterialMap); {
avatar.CultivationItemsView ??= avatar.CultivationItems.SelectList(i => idMaterialMap.GetValueOrDefault(i, Material.Default)!); avatar.Collocation = hutaoCache.AvatarCollocations.GetValueOrDefault(avatar.Id);
} avatar.CookBonusView ??= CookBonusView.Create(avatar.FetterInfo.CookBonus, idMaterialMap);
avatar.CultivationItemsView ??= avatar.CultivationItems.SelectList(i => idMaterialMap.GetValueOrDefault(i, Material.Default));
} }
} }
[Command("CultivateCommand")] [Command("CultivateCommand")]
private async Task CultivateAsync(Avatar? avatar) private async Task CultivateAsync(Avatar? avatar)
{ {
if (avatar != null) if (avatar is null)
{ {
IInfoBarService infoBarService = serviceProvider.GetRequiredService<IInfoBarService>(); return;
IUserService userService = serviceProvider.GetRequiredService<IUserService>(); }
if (userService.Current != null) if (userService.Current is null)
{
infoBarService.Warning(SH.MustSelectUserAndUid);
return;
}
// ContentDialog must be created by main thread.
await taskContext.SwitchToMainThreadAsync();
CalculableOptions options = new(avatar.ToCalculable(), null);
CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance<CultivatePromotionDeltaDialog>(options);
(bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
if (!isOk)
{
return;
}
Response<CalculateConsumption> consumptionResponse = await serviceProvider
.GetRequiredService<CalculateClient>()
.ComputeAsync(userService.Current.Entity, delta)
.ConfigureAwait(false);
if (!consumptionResponse.IsOk())
{
return;
}
CalculateConsumption consumption = consumptionResponse.Data;
List<CalculateItem> items = CalculateItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
try
{
bool saved = await serviceProvider
.GetRequiredService<ICultivationService>()
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
.ConfigureAwait(false);
if (saved)
{ {
// ContentDialog must be created by main thread. infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
await taskContext.SwitchToMainThreadAsync();
CalculableOptions options = new(avatar.ToCalculable(), null);
CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance<CultivatePromotionDeltaDialog>(options);
(bool isOk, CalcAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
if (isOk)
{
Response<CalcConsumption> consumptionResponse = await serviceProvider
.GetRequiredService<CalcClient>()
.ComputeAsync(userService.Current.Entity, delta)
.ConfigureAwait(false);
if (consumptionResponse.IsOk())
{
CalcConsumption consumption = consumptionResponse.Data;
List<CalcItem> items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
try
{
bool saved = await serviceProvider
.GetRequiredService<ICultivationService>()
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
.ConfigureAwait(false);
if (saved)
{
infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
}
else
{
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
}
}
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
}
}
}
} }
else else
{ {
infoBarService.Warning(SH.MustSelectUserAndUid); infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
} }
} }
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
}
} }
private void UpdateBaseValueInfo(Avatar? avatar) private void UpdateBaseValueInfo(Avatar? avatar)
{ {
if (avatar == null) if (avatar is null)
{ {
BaseValueInfo = null; BaseValueInfo = null;
return;
} }
else
ArgumentNullException.ThrowIfNull(promotes);
Dictionary<PromoteLevel, Promote> avatarPromoteMap = promotes.Where(p => p.Id == avatar.PromoteId).ToDictionary(p => p.Level);
Dictionary<FightProperty, GrowCurveType> avatarGrowCurve = avatar.GrowCurves.ToDictionary(g => g.Type, g => g.Value);
FightProperty promoteProperty = avatarPromoteMap[0].AddProperties.Last().Type;
List<PropertyCurveValue> propertyCurveValues = new()
{ {
Dictionary<PromoteLevel, Promote> avatarPromoteMap = promotes!.Where(p => p.Id == avatar.PromoteId).ToDictionary(p => p.Level); new(FightProperty.FIGHT_PROP_BASE_HP, avatarGrowCurve, avatar.BaseValue),
Dictionary<FightProperty, GrowCurveType> avatarGrowCurve = avatar.GrowCurves.ToDictionary(g => g.Type, g => g.Value); new(FightProperty.FIGHT_PROP_BASE_ATTACK, avatarGrowCurve, avatar.BaseValue),
FightProperty promoteProperty = avatarPromoteMap[0].AddProperties.Last().Type; new(FightProperty.FIGHT_PROP_BASE_DEFENSE, avatarGrowCurve, avatar.BaseValue),
new(promoteProperty, avatarGrowCurve, avatar.BaseValue),
};
List<PropertyCurveValue> propertyCurveValues = new() ArgumentNullException.ThrowIfNull(levelAvatarCurveMap);
{ BaseValueInfo = new(avatar.MaxLevel, propertyCurveValues, levelAvatarCurveMap, avatarPromoteMap);
new(FightProperty.FIGHT_PROP_BASE_HP, avatarGrowCurve[FightProperty.FIGHT_PROP_BASE_HP], avatar.BaseValue.HpBase),
new(FightProperty.FIGHT_PROP_BASE_ATTACK, avatarGrowCurve[FightProperty.FIGHT_PROP_BASE_ATTACK], avatar.BaseValue.AttackBase),
new(FightProperty.FIGHT_PROP_BASE_DEFENSE, avatarGrowCurve[FightProperty.FIGHT_PROP_BASE_DEFENSE], avatar.BaseValue.DefenseBase),
new(promoteProperty, GrowCurveType.GROW_CURVE_NONE, 0),
};
BaseValueInfo = new(avatar.MaxLevel, propertyCurveValues, levelAvatarCurveMap!, avatarPromoteMap);
}
} }
[Command("FilterCommand")] [Command("FilterCommand")]
private void ApplyFilter(string? input) private void ApplyFilter(string? input)
{ {
if (Avatars != null) if (Avatars is null)
{ {
if (!string.IsNullOrWhiteSpace(input)) return;
{ }
Avatars.Filter = AvatarFilter.Compile(input);
if (!Avatars.Contains(Selected)) if (string.IsNullOrWhiteSpace(input))
{ {
try Avatars.Filter = default!;
{ return;
Avatars.MoveCurrentToFirst(); }
}
catch (COMException) Avatars.Filter = AvatarFilter.Compile(input);
{
} if (Avatars.Contains(Selected))
} {
} return;
else }
{
Avatars.Filter = null!; try
} {
Avatars.MoveCurrentToFirst();
}
catch (COMException)
{
} }
} }
} }

View File

@@ -50,8 +50,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
/// </summary> /// </summary>
public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); } public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); }
/// <inheritdoc/> protected override async ValueTask<bool> InitializeUIAsync()
protected override async Task OpenUIAsync()
{ {
if (await metadataService.InitializeAsync().ConfigureAwait(false)) if (await metadataService.InitializeAsync().ConfigureAwait(false))
{ {
@@ -61,30 +60,33 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
Dictionary<MaterialId, DisplayItem> idDisplayMap = await metadataService.GetIdToDisplayItemAndMaterialMapAsync().ConfigureAwait(false); Dictionary<MaterialId, DisplayItem> idDisplayMap = await metadataService.GetIdToDisplayItemAndMaterialMapAsync().ConfigureAwait(false);
foreach (Monster monster in monsters) foreach (Monster monster in monsters)
{ {
monster.DropsView ??= monster.Drops?.SelectList(i => idDisplayMap.GetValueOrDefault(i)!); monster.DropsView ??= monster.Drops?.SelectList(i => idDisplayMap.GetValueOrDefault(i, Material.Default));
} }
List<Monster> ordered = monsters.OrderBy(m => m.Id.Value).ToList(); List<Monster> ordered = monsters.SortBy(m => m.Id.Value);
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
Monsters = new AdvancedCollectionView(ordered, true); Monsters = new AdvancedCollectionView(ordered, true);
Selected = Monsters.Cast<Monster>().FirstOrDefault(); Selected = Monsters.Cast<Monster>().FirstOrDefault();
return true;
} }
return false;
} }
private void UpdateBaseValueInfo(Monster? monster) private void UpdateBaseValueInfo(Monster? monster)
{ {
if (monster == null) if (monster is null)
{ {
BaseValueInfo = null; BaseValueInfo = null;
} }
else else
{ {
List<PropertyCurveValue> propertyCurveValues = monster.GrowCurves List<PropertyCurveValue> propertyCurveValues = monster.GrowCurves
.Select(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, monster.BaseValue.GetValue(curveInfo.Type))) .SelectList(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, monster.BaseValue.GetValue(curveInfo.Type)));
.ToList();
BaseValueInfo = new(100, propertyCurveValues, levelMonsterCurveMap!); ArgumentNullException.ThrowIfNull(levelMonsterCurveMap);
BaseValueInfo = new(Monster.MaxLevel, propertyCurveValues, levelMonsterCurveMap);
} }
} }
} }

View File

@@ -14,13 +14,12 @@ using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User; using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog; using Snap.Hutao.View.Dialog;
using Snap.Hutao.ViewModel.Complex;
using Snap.Hutao.Web.Response; using Snap.Hutao.Web.Response;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption; using CalculateConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
namespace Snap.Hutao.ViewModel.Wiki; namespace Snap.Hutao.ViewModel.Wiki;
@@ -31,16 +30,12 @@ namespace Snap.Hutao.ViewModel.Wiki;
[Injection(InjectAs.Scoped)] [Injection(InjectAs.Scoped)]
internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
{ {
private static readonly List<WeaponId> SkippedWeapons = new()
{
12304, 14306, 15306, 13304, // 石英大剑, 琥珀玥, 黑檀弓, 「旗杆」
11419, 11420, 11421, // 「一心传」名刀
};
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly IMetadataService metadataService; private readonly IMetadataService metadataService;
private readonly IHutaoCache hutaoCache; private readonly IHutaoCache hutaoCache;
private readonly IInfoBarService infoBarService;
private readonly IUserService userService;
private AdvancedCollectionView? weapons; private AdvancedCollectionView? weapons;
private Weapon? selected; private Weapon? selected;
@@ -88,7 +83,6 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
List<Weapon> weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false); List<Weapon> weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false);
List<Weapon> sorted = weapons List<Weapon> sorted = weapons
.Where(weapon => !SkippedWeapons.Contains(weapon.Id))
.OrderByDescending(weapon => weapon.RankLevel) .OrderByDescending(weapon => weapon.RankLevel)
.ThenBy(weapon => weapon.WeaponType) .ThenBy(weapon => weapon.WeaponType)
.ThenByDescending(weapon => weapon.Id.Value) .ThenByDescending(weapon => weapon.Id.Value)
@@ -103,114 +97,117 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
} }
} }
private async Task CombineWithWeaponCollocationsAsync(List<Weapon> weapons) private async ValueTask CombineWithWeaponCollocationsAsync(List<Weapon> weapons)
{ {
if (await hutaoCache.InitializeForWikiWeaponViewModelAsync().ConfigureAwait(false)) if (await hutaoCache.InitializeForWikiWeaponViewModelAsync().ConfigureAwait(false))
{ {
Dictionary<WeaponId, WeaponCollocationView> idCollocations = hutaoCache.WeaponCollocations!.ToDictionary(a => a.WeaponId); ArgumentNullException.ThrowIfNull(hutaoCache.WeaponCollocations);
weapons.ForEach(w => w.Collocation = idCollocations.GetValueOrDefault(w.Id)); weapons.ForEach(w => w.Collocation = hutaoCache.WeaponCollocations.GetValueOrDefault(w.Id));
} }
} }
[Command("CultivateCommand")] [Command("CultivateCommand")]
private async Task CultivateAsync(Weapon? weapon) private async Task CultivateAsync(Weapon? weapon)
{ {
if (weapon != null) if (weapon is null)
{ {
IInfoBarService infoBarService = serviceProvider.GetRequiredService<IInfoBarService>(); return;
IUserService userService = serviceProvider.GetRequiredService<IUserService>(); }
if (userService.Current != null) if (userService.Current is null)
{
infoBarService.Warning(SH.MustSelectUserAndUid);
return;
}
// ContentDialog must be created by main thread.
await taskContext.SwitchToMainThreadAsync();
CalculableOptions options = new(null, weapon.ToCalculable());
CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance<CultivatePromotionDeltaDialog>(options);
(bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
if (!isOk)
{
return;
}
Response<CalculateConsumption> consumptionResponse = await serviceProvider
.GetRequiredService<CalculateClient>()
.ComputeAsync(userService.Current.Entity, delta)
.ConfigureAwait(false);
if (!consumptionResponse.IsOk())
{
return;
}
CalculateConsumption consumption = consumptionResponse.Data;
try
{
bool saved = await serviceProvider
.GetRequiredService<ICultivationService>()
.SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, consumption.WeaponConsume.EmptyIfNull())
.ConfigureAwait(false);
if (saved)
{ {
// ContentDialog must be created by main thread. infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
await taskContext.SwitchToMainThreadAsync();
CalculableOptions options = new(null, weapon.ToCalculable());
CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance<CultivatePromotionDeltaDialog>(options);
(bool isOk, CalcAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
if (isOk)
{
Response<CalcConsumption> consumptionResponse = await serviceProvider
.GetRequiredService<CalcClient>()
.ComputeAsync(userService.Current.Entity, delta)
.ConfigureAwait(false);
if (consumptionResponse.IsOk())
{
CalcConsumption consumption = consumptionResponse.Data;
try
{
bool saved = await serviceProvider
.GetRequiredService<ICultivationService>()
.SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, consumption.WeaponConsume.EmptyIfNull())
.ConfigureAwait(false);
if (saved)
{
infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
}
else
{
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
}
}
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
}
}
}
} }
else else
{ {
infoBarService.Warning(SH.MustSelectUserAndUid); infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
} }
} }
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
}
} }
private void UpdateBaseValueInfo(Weapon? weapon) private void UpdateBaseValueInfo(Weapon? weapon)
{ {
if (weapon == null) if (weapon is null)
{ {
BaseValueInfo = null; BaseValueInfo = null;
return;
} }
else
{
Dictionary<PromoteLevel, Promote> weaponPromoteMap = promotes!.Where(p => p.Id == weapon.PromoteId).ToDictionary(p => p.Level);
List<PropertyCurveValue> propertyCurveValues = weapon.GrowCurves ArgumentNullException.ThrowIfNull(promotes);
.Select(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, curveInfo.InitValue)) Dictionary<PromoteLevel, Promote> weaponPromoteMap = promotes.Where(p => p.Id == weapon.PromoteId).ToDictionary(p => p.Level);
.ToList(); List<PropertyCurveValue> propertyCurveValues = weapon.GrowCurves
.SelectList(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, curveInfo.InitValue));
BaseValueInfo = new(weapon.MaxLevel, propertyCurveValues, levelWeaponCurveMap!, weaponPromoteMap); ArgumentNullException.ThrowIfNull(levelWeaponCurveMap);
} BaseValueInfo = new(weapon.MaxLevel, propertyCurveValues, levelWeaponCurveMap, weaponPromoteMap);
} }
[Command("FilterCommand")] [Command("FilterCommand")]
private void ApplyFilter(string? input) private void ApplyFilter(string? input)
{ {
if (Weapons != null) if (Weapons is null)
{ {
if (!string.IsNullOrWhiteSpace(input)) return;
{ }
Weapons.Filter = WeaponFilter.Compile(input);
if (!Weapons.Contains(Selected)) if (string.IsNullOrWhiteSpace(input))
{ {
try Weapons.Filter = default!;
{ return;
Weapons.MoveCurrentToFirst(); }
}
catch (COMException) Weapons.Filter = WeaponFilter.Compile(input);
{
} if (Weapons.Contains(Selected))
} {
} return;
else }
{
Weapons.Filter = null!; try
} {
Weapons.MoveCurrentToFirst();
}
catch (COMException)
{
} }
} }
} }