diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarBaseValue.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarBaseValue.cs
index 0b62a1b3..03508599 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarBaseValue.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarBaseValue.cs
@@ -1,10 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Model.Intrinsic;
-using Snap.Hutao.Model.Primitive;
-using Snap.Hutao.ViewModel.Wiki;
namespace Snap.Hutao.Model.Metadata.Avatar;
@@ -13,4 +10,13 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
///
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),
+ };
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/Monster.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/Monster.cs
index 3ddab034..b7af48ea 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/Monster.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/Monster.cs
@@ -12,6 +12,8 @@ namespace Snap.Hutao.Model.Metadata.Monster;
///
internal sealed class Monster
{
+ internal const uint MaxLevel = 100;
+
///
/// Id
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Promote.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Promote.cs
index b61de4af..60368338 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Promote.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Promote.cs
@@ -29,11 +29,9 @@ internal sealed class Promote
///
public List> AddProperties { get; set; } = default!;
- ///
- /// 属性映射
- ///
- public Dictionary AddPropertyMap
+ public float GetValue(FightProperty property)
{
- get => addPropertyMap ??= AddProperties.ToDictionary(a => a.Type, a => a.Value);
+ addPropertyMap ??= AddProperties.ToDictionary(a => a.Type, a => a.Value);
+ return addPropertyMap.GetValueOrDefault(property);
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/LevelFormat.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/LevelFormat.cs
new file mode 100644
index 00000000..ac519875
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/LevelFormat.cs
@@ -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}";
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
index 6809b4d0..f172b865 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
@@ -45,10 +45,10 @@ internal sealed partial class HutaoCache : IHutaoCache
public Overview? Overview { get; set; }
///
- public List? AvatarCollocations { get; set; }
+ public Dictionary? AvatarCollocations { get; set; }
///
- public List? WeaponCollocations { get; set; }
+ public Dictionary? WeaponCollocations { get; set; }
///
public async ValueTask InitializeForDatabaseViewModelAsync()
@@ -153,7 +153,7 @@ internal sealed partial class HutaoCache : IHutaoCache
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
Weapons = co.Weapons.SelectList(w => new WeaponView(idWeaponMap[w.Item], w.Rate)),
ReliquarySets = co.Reliquaries.SelectList(r => new ReliquarySetView(r, idReliquarySetMap)),
- });
+ }).ToDictionary(a => a.AvatarId);
}
private async ValueTask WeaponCollocationsAsync(Dictionary idAvatarMap)
@@ -169,7 +169,7 @@ internal sealed partial class HutaoCache : IHutaoCache
{
WeaponId = co.WeaponId,
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
- });
+ }).ToDictionary(w => w.WeaponId);
}
[SuppressMessage("", "SH003")]
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs
index 5c3125db..79741833 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding.Hutao;
+using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.Complex;
using Snap.Hutao.Web.Hutao.Model;
@@ -41,12 +42,12 @@ internal interface IHutaoCache
///
/// 角色搭配
///
- List? AvatarCollocations { get; set; }
+ Dictionary? AvatarCollocations { get; set; }
///
/// 武器搭配
///
- List? WeaponCollocations { get; set; }
+ Dictionary? WeaponCollocations { get; set; }
///
/// 为数据库视图模型初始化
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs
new file mode 100644
index 00000000..18b906e4
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs
@@ -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 CreateOrDefaultUserFromCookieAsync(Cookie cookie, bool isOversea, CancellationToken token = default(CancellationToken));
+
+ ValueTask ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default(CancellationToken));
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs
new file mode 100644
index 00000000..baa26503
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs
@@ -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 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 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 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 TrySetUserLTokenAsync(ViewModel.User.User user, CancellationToken token)
+ {
+ if (user.LToken is not null)
+ {
+ return true;
+ }
+
+ Response lTokenResponse = await serviceProvider
+ .GetRequiredService>()
+ .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 TrySetUserCookieTokenAsync(ViewModel.User.User user, CancellationToken token)
+ {
+ if (user.CookieToken is not null)
+ {
+ return true;
+ }
+
+ Response cookieTokenResponse = await serviceProvider
+ .GetRequiredService>()
+ .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 TrySetUserUserInfoAsync(ViewModel.User.User user, CancellationToken token)
+ {
+ Response response = await serviceProvider
+ .GetRequiredService>()
+ .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 TrySetUserUserGameRolesAsync(ViewModel.User.User user, CancellationToken token)
+ {
+ Response> userGameRolesResponse = await serviceProvider
+ .GetRequiredService()
+ .GetUserGameRolesOverseaAwareAsync(user.Entity, token)
+ .ConfigureAwait(false);
+
+ if (userGameRolesResponse.IsOk())
+ {
+ user.UserGameRoles = userGameRolesResponse.Data.List;
+ return user.UserGameRoles.Any();
+ }
+ else
+ {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
index f3a1bd53..45963999 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
@@ -25,11 +25,12 @@ namespace Snap.Hutao.Service.User;
[Injection(InjectAs.Singleton, typeof(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 dbCurrent;
+ private readonly IUserInitializationService userInitializationService;
+ private readonly IServiceProvider serviceProvider;
+ private readonly IUserDbService userDbService;
+ private readonly ITaskContext taskContext;
+ private readonly IMessenger messenger;
private ObservableCollection? userCollection;
private ObservableCollection? userAndUidCollection;
@@ -63,7 +64,7 @@ internal sealed partial class UserService : IUserService
if (userCollection is null)
{
List entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
- List users = await entities.SelectListAsync(BindingUser.ResumeAsync, default).ConfigureAwait(false);
+ List users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync, default).ConfigureAwait(false);
userCollection = users.ToObservableCollection();
try
@@ -72,7 +73,7 @@ internal sealed partial class UserService : IUserService
}
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> TryCreateUserAndAddAsync(Cookie cookie, bool isOversea)
{
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)
{
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs
index 4fd0e922..48d626f0 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs
@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
+using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Model;
@@ -17,34 +18,36 @@ namespace Snap.Hutao.ViewModel.User;
///
/// 用于视图绑定的用户
-/// TODO: move initializaion part to service
///
[HighQuality]
-internal sealed class User : ObservableObject, IEntityOnly, ISelectable
+internal sealed class User : ObservableObject, IEntityOnly, IMappingFrom, ISelectable
{
private readonly EntityUser inner;
+ private readonly IServiceProvider serviceProvider;
private UserGameRole? selectedUserGameRole;
- private bool isInitialized;
///
/// 构造一个新的绑定视图用户
///
/// 用户实体
- private User(EntityUser user)
+ private User(EntityUser user, IServiceProvider serviceProvider)
{
inner = user;
+ this.serviceProvider = serviceProvider;
}
- ///
- /// 用户信息
- ///
- public UserInfo? UserInfo { get; private set; }
+ public bool IsInitialized { get; set; }
///
/// 用户信息
///
- public List UserGameRoles { get; private set; } = default!;
+ public UserInfo? UserInfo { get; set; }
+
+ ///
+ /// 用户信息
+ ///
+ public List UserGameRoles { get; set; } = default!;
///
/// 用户信息, 请勿访问set
@@ -56,9 +59,8 @@ internal sealed class User : ObservableObject, IEntityOnly, ISelecta
{
if (SetProperty(ref selectedUserGameRole, value))
{
- Ioc.Default
- .GetRequiredService()
- .Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this });
+ IMessenger messenger = serviceProvider.GetRequiredService();
+ messenger.Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this });
}
}
}
@@ -103,178 +105,8 @@ internal sealed class User : ObservableObject, IEntityOnly, ISelecta
///
public EntityUser Entity { get => inner; }
- ///
- /// 从数据库恢复用户
- ///
- /// 数据库实体
- /// 取消令牌
- /// 用户
- internal static async ValueTask ResumeAsync(EntityUser inner, CancellationToken token = default)
+ public static User From(EntityUser user, IServiceProvider provider)
{
- User user = new(inner);
-
- if (!await user.InitializeCoreAsync(token).ConfigureAwait(false))
- {
- user.UserInfo = new() { Nickname = SH.ModelBindingUserInitializationFailed };
- user.UserGameRoles = new();
- }
-
- return user;
- }
-
- ///
- /// 创建并初始化用户
- ///
- /// cookie
- /// 是否为国际服
- /// 取消令牌
- /// 用户
- internal static async Task 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 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 TrySetLTokenAsync(IServiceProvider provider, CancellationToken token)
- {
- if (LToken != null)
- {
- return true;
- }
-
- Response lTokenResponse = await provider
- .GetRequiredService>()
- .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 TrySetCookieTokenAsync(IServiceProvider provider, CancellationToken token)
- {
- if (CookieToken != null)
- {
- return true;
- }
-
- Response cookieTokenResponse = await provider
- .GetRequiredService>()
- .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 TrySetUserInfoAsync(IServiceProvider provider, CancellationToken token)
- {
- Response response = await provider
- .GetRequiredService>()
- .Create(Entity.IsOversea)
- .GetUserFullInfoAsync(Entity, token)
- .ConfigureAwait(false);
-
- if (response.IsOk())
- {
- UserInfo = response.Data.UserInfo;
- return true;
- }
- else
- {
- return false;
- }
- }
-
- private async Task TrySetUserGameRolesAsync(IServiceProvider provider, CancellationToken token)
- {
- Response> userGameRolesResponse = await provider
- .GetRequiredService()
- .GetUserGameRolesOverseaAwareAsync(Entity, token)
- .ConfigureAwait(false);
-
- if (userGameRolesResponse.IsOk())
- {
- UserGameRoles = userGameRolesResponse.Data.List;
- return UserGameRoles.Any();
- }
- else
- {
- return false;
- }
+ return new(user, provider);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs
index 436e914d..2003ace6 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
+using Snap.Hutao.Core;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Service.Navigation;
@@ -23,9 +24,10 @@ namespace Snap.Hutao.ViewModel.User;
[Injection(InjectAs.Singleton)]
internal sealed partial class UserViewModel : ObservableObject
{
+ private readonly INavigationService navigationService;
private readonly IServiceProvider serviceProvider;
private readonly IInfoBarService infoBarService;
- private readonly Core.RuntimeOptions hutaoOptions;
+ private readonly RuntimeOptions runtimeOptions;
private readonly ITaskContext taskContext;
private readonly IUserService userService;
@@ -44,7 +46,7 @@ internal sealed partial class UserViewModel : ObservableObject
{
userService.Current = value;
- if (value != null)
+ if (value is not null)
{
value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
}
@@ -63,12 +65,13 @@ internal sealed partial class UserViewModel : ObservableObject
/// 操作结果
/// uid
/// 任务
- public async Task HandleUserOptionResultAsync(UserOptionResult optionResult, string uid)
+ internal async ValueTask HandleUserOptionResultAsync(UserOptionResult optionResult, string uid)
{
switch (optionResult)
{
case UserOptionResult.Added:
- if (Users!.Count == 1)
+ ArgumentNullException.ThrowIfNull(Users);
+ if (Users.Count == 1)
{
await taskContext.SwitchToMainThreadAsync();
SelectedUser = Users.Single();
@@ -107,16 +110,16 @@ internal sealed partial class UserViewModel : ObservableObject
[Command("AddUserCommand")]
private Task AddUserAsync()
{
- return AddUserCoreAsync(false);
+ return AddUserCoreAsync(false).AsTask();
}
[Command("AddOverseaUserCommand")]
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.
await taskContext.SwitchToMainThreadAsync();
@@ -126,9 +129,9 @@ internal sealed partial class UserViewModel : ObservableObject
ValueResult result = await dialog.GetInputCookieAsync().ConfigureAwait(false);
// 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);
@@ -139,11 +142,9 @@ internal sealed partial class UserViewModel : ObservableObject
[Command("LoginMihoyoUserCommand")]
private void LoginMihoyoUser()
{
- if (hutaoOptions.IsWebView2Supported)
+ if (runtimeOptions.IsWebView2Supported)
{
- serviceProvider
- .GetRequiredService()
- .Navigate(INavigationAwaiter.Default);
+ navigationService.Navigate(INavigationAwaiter.Default);
}
else
{
@@ -154,11 +155,9 @@ internal sealed partial class UserViewModel : ObservableObject
[Command("LoginHoyoverseUserCommand")]
private void LoginHoyoverseUser()
{
- if (hutaoOptions.IsWebView2Supported)
+ if (runtimeOptions.IsWebView2Supported)
{
- serviceProvider
- .GetRequiredService()
- .Navigate(INavigationAwaiter.Default);
+ navigationService.Navigate(INavigationAwaiter.Default);
}
else
{
@@ -169,11 +168,11 @@ internal sealed partial class UserViewModel : ObservableObject
[Command("RemoveUserCommand")]
private async Task RemoveUserAsync(User? user)
{
- if (user != null)
+ if (user is not null)
{
try
{
- await userService.RemoveUserAsync(user!).ConfigureAwait(false);
+ await userService.RemoveUserAsync(user).ConfigureAwait(false);
infoBarService.Success(string.Format(SH.ViewModelUserRemoved, user.UserInfo?.Nickname));
}
catch (UserdataCorruptedException ex)
@@ -188,26 +187,29 @@ internal sealed partial class UserViewModel : ObservableObject
{
try
{
+ ArgumentNullException.ThrowIfNull(user);
string cookieString = new StringBuilder()
- .Append(user!.SToken)
- .AppendIf(user.SToken != null, ';')
+ .Append(user.SToken)
+ .AppendIf(user.SToken is not null, ';')
.Append(user.LToken)
- .AppendIf(user.LToken != null, ';')
+ .AppendIf(user.LToken is not null, ';')
.Append(user.CookieToken)
.ToString();
serviceProvider.GetRequiredService().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")]
private async Task RefreshCookieTokenAsync()
{
- if (SelectedUser != null)
+ if (SelectedUser is not null)
{
if (await userService.RefreshCookieTokenAsync(SelectedUser).ConfigureAwait(false))
{
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs
index 66dfeda7..33e67f79 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs
@@ -26,6 +26,7 @@ internal static class AvatarFilter
{
List matches = new();
+ // TODO: use Collection Literals
foreach (StringSegment segment in new StringTokenizer(input, new char[] { ' ' }))
{
string value = segment.ToString();
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/BaseValueInfo.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/BaseValueInfo.cs
index 19b5bc04..ede05192 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/BaseValueInfo.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/BaseValueInfo.cs
@@ -71,7 +71,7 @@ internal sealed class BaseValueInfo : ObservableObject
///
public string CurrentLevelFormatted
{
- get => $"Lv.{CurrentLevel}";
+ get => LevelFormat.Format(CurrentLevel);
}
///
@@ -95,10 +95,10 @@ internal sealed class BaseValueInfo : ObservableObject
Values = propValues.SelectList(propValue =>
{
float value = propValue.Value * growCurveMap[level].GetValueOrDefault(propValue.Type);
- if (promoteMap != null)
+ if (promoteMap is not null)
{
PromoteLevel promoteLevel = GetPromoteLevel(level, promoted);
- float addValue = promoteMap[promoteLevel].AddPropertyMap.GetValueOrDefault(propValue.Property, 0F);
+ float addValue = promoteMap[promoteLevel].GetValue(propValue.Property);
value += addValue;
}
@@ -109,59 +109,36 @@ internal sealed class BaseValueInfo : ObservableObject
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
- {
- >= 60U => 4,
- >= 50U => 3,
- >= 40U => 2,
- >= 20U => 1,
- _ => 0,
- };
- }
- else
- {
- return level.Value switch
- {
- > 60U => 4,
- > 50U => 3,
- > 40U => 2,
- > 20U => 1,
- _ => 0,
- };
- }
+ >= 80U => 6,
+ >= 70U => 5,
+ >= 60U => 4,
+ >= 50U => 3,
+ >= 40U => 2,
+ >= 20U => 1,
+ _ => 0,
+ };
}
else
{
- if (promoted)
+ return level.Value switch
{
- return level.Value switch
- {
- >= 80U => 6,
- >= 70U => 5,
- >= 60U => 4,
- >= 50U => 3,
- >= 40U => 2,
- >= 20U => 1,
- _ => 0,
- };
- }
- else
- {
- return level.Value switch
- {
- > 80U => 6,
- > 70U => 5,
- > 60U => 4,
- > 50U => 3,
- > 40U => 2,
- > 20U => 1,
- _ => 0,
- };
- }
+ > 80U => 6,
+ > 70U => 5,
+ > 60U => 4,
+ > 50U => 3,
+ > 40U => 2,
+ > 20U => 1,
+ _ => 0,
+ };
}
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/CookBonusView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/CookBonusView.cs
index 8337de49..ebe6b8fe 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/CookBonusView.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/CookBonusView.cs
@@ -31,7 +31,7 @@ internal sealed class CookBonusView
/// 新的料理奖励视图
public static CookBonusView? Create(CookBonus? cookBonus, Dictionary idMaterialMap)
{
- if (cookBonus == null)
+ if (cookBonus is null)
{
return null;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/PropertyCurveValue.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/PropertyCurveValue.cs
index 99088b81..d1cca049 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/PropertyCurveValue.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/PropertyCurveValue.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
+using Snap.Hutao.Model.Metadata;
namespace Snap.Hutao.ViewModel.Wiki;
@@ -23,6 +24,11 @@ internal sealed class PropertyCurveValue
Value = value;
}
+ public PropertyCurveValue(FightProperty property, Dictionary growCurve, BaseValue baseValue)
+ : this(property, growCurve.GetValueOrDefault(property), baseValue.GetValue(property))
+ {
+ }
+
///
/// 战斗属性值
///
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs
index f2ea8b37..d603d067 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs
@@ -2,11 +2,9 @@
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI;
-using Microsoft.Extensions.Primitives;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Model.Intrinsic;
-using Snap.Hutao.Model.Intrinsic.Immutable;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item;
@@ -17,15 +15,14 @@ using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
-using Snap.Hutao.ViewModel.Complex;
using Snap.Hutao.Web.Response;
using System.Collections.Immutable;
using System.Runtime.InteropServices;
-using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
-using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
-using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
-using CalcItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
-using CalcItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
+using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
+using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
+using CalculateConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
+using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
+using CalculateItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
namespace Snap.Hutao.ViewModel.Wiki;
@@ -41,6 +38,8 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
private readonly IMetadataService metadataService;
private readonly ITaskContext taskContext;
private readonly IHutaoCache hutaoCache;
+ private readonly IInfoBarService infoBarService;
+ private readonly IUserService userService;
private AdvancedCollectionView? avatars;
private Avatar? selected;
@@ -78,150 +77,159 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
///
public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); }
- ///
- protected override async Task OpenUIAsync()
+ protected override async ValueTask InitializeUIAsync()
{
- if (await metadataService.InitializeAsync().ConfigureAwait(false))
+ if (!await metadataService.InitializeAsync().ConfigureAwait(false))
{
- levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false);
- promotes = await metadataService.GetAvatarPromotesAsync().ConfigureAwait(false);
-
- Dictionary idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
- List avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false);
- List 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().FirstOrDefault();
+ return false;
}
+
+ levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false);
+ promotes = await metadataService.GetAvatarPromotesAsync().ConfigureAwait(false);
+
+ Dictionary idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
+ List avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false);
+ List 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().FirstOrDefault();
+ return true;
}
- private async Task CombineComplexDataAsync(List avatars, Dictionary idMaterialMap)
+ private async ValueTask CombineComplexDataAsync(List avatars, Dictionary idMaterialMap)
{
- if (await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false))
+ if (!await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false))
{
- Dictionary idCollocations = hutaoCache.AvatarCollocations!.ToDictionary(a => a.AvatarId);
+ return;
+ }
- foreach (Avatar avatar in avatars)
- {
- avatar.Collocation = idCollocations.GetValueOrDefault(avatar.Id);
- avatar.CookBonusView ??= CookBonusView.Create(avatar.FetterInfo.CookBonus, idMaterialMap);
- avatar.CultivationItemsView ??= avatar.CultivationItems.SelectList(i => idMaterialMap.GetValueOrDefault(i, Material.Default)!);
- }
+ ArgumentNullException.ThrowIfNull(hutaoCache.AvatarCollocations);
+
+ foreach (Avatar avatar in avatars)
+ {
+ 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")]
private async Task CultivateAsync(Avatar? avatar)
{
- if (avatar != null)
+ if (avatar is null)
{
- IInfoBarService infoBarService = serviceProvider.GetRequiredService();
- IUserService userService = serviceProvider.GetRequiredService();
+ return;
+ }
- 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(options);
+ (bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
+
+ if (!isOk)
+ {
+ return;
+ }
+
+ Response consumptionResponse = await serviceProvider
+ .GetRequiredService()
+ .ComputeAsync(userService.Current.Entity, delta)
+ .ConfigureAwait(false);
+
+ if (!consumptionResponse.IsOk())
+ {
+ return;
+ }
+
+ CalculateConsumption consumption = consumptionResponse.Data;
+ List items = CalculateItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
+ try
+ {
+ bool saved = await serviceProvider
+ .GetRequiredService()
+ .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
+ .ConfigureAwait(false);
+
+ if (saved)
{
- // ContentDialog must be created by main thread.
- await taskContext.SwitchToMainThreadAsync();
- CalculableOptions options = new(avatar.ToCalculable(), null);
- CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance(options);
- (bool isOk, CalcAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
-
- if (isOk)
- {
- Response consumptionResponse = await serviceProvider
- .GetRequiredService()
- .ComputeAsync(userService.Current.Entity, delta)
- .ConfigureAwait(false);
-
- if (consumptionResponse.IsOk())
- {
- CalcConsumption consumption = consumptionResponse.Data;
-
- List items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
- try
- {
- bool saved = await serviceProvider
- .GetRequiredService()
- .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);
- }
- }
- }
+ infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
}
else
{
- infoBarService.Warning(SH.MustSelectUserAndUid);
+ infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
}
}
+ catch (Core.ExceptionService.UserdataCorruptedException ex)
+ {
+ infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
+ }
}
private void UpdateBaseValueInfo(Avatar? avatar)
{
- if (avatar == null)
+ if (avatar is null)
{
BaseValueInfo = null;
+ return;
}
- else
+
+ ArgumentNullException.ThrowIfNull(promotes);
+ Dictionary avatarPromoteMap = promotes.Where(p => p.Id == avatar.PromoteId).ToDictionary(p => p.Level);
+ Dictionary avatarGrowCurve = avatar.GrowCurves.ToDictionary(g => g.Type, g => g.Value);
+ FightProperty promoteProperty = avatarPromoteMap[0].AddProperties.Last().Type;
+
+ List propertyCurveValues = new()
{
- Dictionary avatarPromoteMap = promotes!.Where(p => p.Id == avatar.PromoteId).ToDictionary(p => p.Level);
- Dictionary avatarGrowCurve = avatar.GrowCurves.ToDictionary(g => g.Type, g => g.Value);
- FightProperty promoteProperty = avatarPromoteMap[0].AddProperties.Last().Type;
+ new(FightProperty.FIGHT_PROP_BASE_HP, avatarGrowCurve, avatar.BaseValue),
+ new(FightProperty.FIGHT_PROP_BASE_ATTACK, avatarGrowCurve, avatar.BaseValue),
+ new(FightProperty.FIGHT_PROP_BASE_DEFENSE, avatarGrowCurve, avatar.BaseValue),
+ new(promoteProperty, avatarGrowCurve, avatar.BaseValue),
+ };
- List propertyCurveValues = new()
- {
- 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);
- }
+ ArgumentNullException.ThrowIfNull(levelAvatarCurveMap);
+ BaseValueInfo = new(avatar.MaxLevel, propertyCurveValues, levelAvatarCurveMap, avatarPromoteMap);
}
[Command("FilterCommand")]
private void ApplyFilter(string? input)
{
- if (Avatars != null)
+ if (Avatars is null)
{
- if (!string.IsNullOrWhiteSpace(input))
- {
- Avatars.Filter = AvatarFilter.Compile(input);
+ return;
+ }
- if (!Avatars.Contains(Selected))
- {
- try
- {
- Avatars.MoveCurrentToFirst();
- }
- catch (COMException)
- {
- }
- }
- }
- else
- {
- Avatars.Filter = null!;
- }
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ Avatars.Filter = default!;
+ return;
+ }
+
+ Avatars.Filter = AvatarFilter.Compile(input);
+
+ if (Avatars.Contains(Selected))
+ {
+ return;
+ }
+
+ try
+ {
+ Avatars.MoveCurrentToFirst();
+ }
+ catch (COMException)
+ {
}
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs
index 9912e1ab..926f9402 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs
@@ -50,8 +50,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
///
public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); }
- ///
- protected override async Task OpenUIAsync()
+ protected override async ValueTask InitializeUIAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
@@ -61,30 +60,33 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
Dictionary idDisplayMap = await metadataService.GetIdToDisplayItemAndMaterialMapAsync().ConfigureAwait(false);
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 ordered = monsters.OrderBy(m => m.Id.Value).ToList();
+ List ordered = monsters.SortBy(m => m.Id.Value);
await taskContext.SwitchToMainThreadAsync();
Monsters = new AdvancedCollectionView(ordered, true);
Selected = Monsters.Cast().FirstOrDefault();
+ return true;
}
+
+ return false;
}
private void UpdateBaseValueInfo(Monster? monster)
{
- if (monster == null)
+ if (monster is null)
{
BaseValueInfo = null;
}
else
{
List propertyCurveValues = monster.GrowCurves
- .Select(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, monster.BaseValue.GetValue(curveInfo.Type)))
- .ToList();
+ .SelectList(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, monster.BaseValue.GetValue(curveInfo.Type)));
- BaseValueInfo = new(100, propertyCurveValues, levelMonsterCurveMap!);
+ ArgumentNullException.ThrowIfNull(levelMonsterCurveMap);
+ BaseValueInfo = new(Monster.MaxLevel, propertyCurveValues, levelMonsterCurveMap);
}
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs
index 69e07f90..32a19dea 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs
@@ -14,13 +14,12 @@ using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
-using Snap.Hutao.ViewModel.Complex;
using Snap.Hutao.Web.Response;
using System.Collections.Immutable;
using System.Runtime.InteropServices;
-using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
-using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
-using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
+using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
+using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
+using CalculateConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
namespace Snap.Hutao.ViewModel.Wiki;
@@ -31,16 +30,12 @@ namespace Snap.Hutao.ViewModel.Wiki;
[Injection(InjectAs.Scoped)]
internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
{
- private static readonly List SkippedWeapons = new()
- {
- 12304, 14306, 15306, 13304, // 石英大剑, 琥珀玥, 黑檀弓, 「旗杆」
- 11419, 11420, 11421, // 「一心传」名刀
- };
-
private readonly ITaskContext taskContext;
private readonly IServiceProvider serviceProvider;
private readonly IMetadataService metadataService;
private readonly IHutaoCache hutaoCache;
+ private readonly IInfoBarService infoBarService;
+ private readonly IUserService userService;
private AdvancedCollectionView? weapons;
private Weapon? selected;
@@ -88,7 +83,6 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
List weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false);
List sorted = weapons
- .Where(weapon => !SkippedWeapons.Contains(weapon.Id))
.OrderByDescending(weapon => weapon.RankLevel)
.ThenBy(weapon => weapon.WeaponType)
.ThenByDescending(weapon => weapon.Id.Value)
@@ -103,114 +97,117 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
}
}
- private async Task CombineWithWeaponCollocationsAsync(List weapons)
+ private async ValueTask CombineWithWeaponCollocationsAsync(List weapons)
{
if (await hutaoCache.InitializeForWikiWeaponViewModelAsync().ConfigureAwait(false))
{
- Dictionary idCollocations = hutaoCache.WeaponCollocations!.ToDictionary(a => a.WeaponId);
- weapons.ForEach(w => w.Collocation = idCollocations.GetValueOrDefault(w.Id));
+ ArgumentNullException.ThrowIfNull(hutaoCache.WeaponCollocations);
+ weapons.ForEach(w => w.Collocation = hutaoCache.WeaponCollocations.GetValueOrDefault(w.Id));
}
}
[Command("CultivateCommand")]
private async Task CultivateAsync(Weapon? weapon)
{
- if (weapon != null)
+ if (weapon is null)
{
- IInfoBarService infoBarService = serviceProvider.GetRequiredService();
- IUserService userService = serviceProvider.GetRequiredService();
+ return;
+ }
- 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(options);
+ (bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
+
+ if (!isOk)
+ {
+ return;
+ }
+
+ Response consumptionResponse = await serviceProvider
+ .GetRequiredService()
+ .ComputeAsync(userService.Current.Entity, delta)
+ .ConfigureAwait(false);
+
+ if (!consumptionResponse.IsOk())
+ {
+ return;
+ }
+
+ CalculateConsumption consumption = consumptionResponse.Data;
+ try
+ {
+ bool saved = await serviceProvider
+ .GetRequiredService()
+ .SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, consumption.WeaponConsume.EmptyIfNull())
+ .ConfigureAwait(false);
+
+ if (saved)
{
- // ContentDialog must be created by main thread.
- await taskContext.SwitchToMainThreadAsync();
- CalculableOptions options = new(null, weapon.ToCalculable());
- CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance(options);
- (bool isOk, CalcAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
-
- if (isOk)
- {
- Response consumptionResponse = await serviceProvider
- .GetRequiredService()
- .ComputeAsync(userService.Current.Entity, delta)
- .ConfigureAwait(false);
-
- if (consumptionResponse.IsOk())
- {
- CalcConsumption consumption = consumptionResponse.Data;
-
- try
- {
- bool saved = await serviceProvider
- .GetRequiredService()
- .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);
- }
- }
- }
+ infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
}
else
{
- infoBarService.Warning(SH.MustSelectUserAndUid);
+ infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
}
}
+ catch (Core.ExceptionService.UserdataCorruptedException ex)
+ {
+ infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
+ }
}
private void UpdateBaseValueInfo(Weapon? weapon)
{
- if (weapon == null)
+ if (weapon is null)
{
BaseValueInfo = null;
+ return;
}
- else
- {
- Dictionary weaponPromoteMap = promotes!.Where(p => p.Id == weapon.PromoteId).ToDictionary(p => p.Level);
- List propertyCurveValues = weapon.GrowCurves
- .Select(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, curveInfo.InitValue))
- .ToList();
+ ArgumentNullException.ThrowIfNull(promotes);
+ Dictionary weaponPromoteMap = promotes.Where(p => p.Id == weapon.PromoteId).ToDictionary(p => p.Level);
+ List 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")]
private void ApplyFilter(string? input)
{
- if (Weapons != null)
+ if (Weapons is null)
{
- if (!string.IsNullOrWhiteSpace(input))
- {
- Weapons.Filter = WeaponFilter.Compile(input);
+ return;
+ }
- if (!Weapons.Contains(Selected))
- {
- try
- {
- Weapons.MoveCurrentToFirst();
- }
- catch (COMException)
- {
- }
- }
- }
- else
- {
- Weapons.Filter = null!;
- }
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ Weapons.Filter = default!;
+ return;
+ }
+
+ Weapons.Filter = WeaponFilter.Compile(input);
+
+ if (Weapons.Contains(Selected))
+ {
+ return;
+ }
+
+ try
+ {
+ Weapons.MoveCurrentToFirst();
+ }
+ catch (COMException)
+ {
}
}
}
\ No newline at end of file