diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
index 4fde20be..bb05fa6d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
+using Snap.Hutao.Extension;
namespace Snap.Hutao.Core.Database;
@@ -14,7 +15,7 @@ namespace Snap.Hutao.Core.Database;
/// 消息的类型
internal class DbCurrent
where TEntity : class, ISelectable
- where TMessage : Message.ValueChangedMessage
+ where TMessage : Message.ValueChangedMessage, new()
{
private readonly DbContext dbContext;
private readonly DbSet dbSet;
@@ -25,12 +26,12 @@ internal class DbCurrent
///
/// 构造一个新的数据库当前项
///
- /// 数据库上下文
/// 数据集
/// 消息器
- public DbCurrent(DbContext dbContext, DbSet dbSet, IMessenger messenger)
+ ///
+ public DbCurrent(DbSet dbSet, IMessenger messenger)
{
- this.dbContext = dbContext;
+ this.dbContext = dbSet.Context();
this.dbSet = dbSet;
this.messenger = messenger;
}
@@ -55,96 +56,18 @@ internal class DbCurrent
if (current != null)
{
current.IsSelected = false;
- dbSet.Update(current);
- dbContext.SaveChanges();
+ dbSet.UpdateAndSave(current);
}
}
- TMessage message = (TMessage)Activator.CreateInstance(typeof(TMessage), current, value)!;
+ TMessage message = new() { OldValue = current, NewValue = value };
+
current = value;
if (current != null)
{
current.IsSelected = true;
- dbSet.Update(current);
- dbContext.SaveChanges();
- }
-
- messenger.Send(message);
- }
- }
-}
-
-///
-/// 数据库当前项
-/// 简化对数据库中选中项的管理
-///
-/// 绑定类型
-/// 实体的类型
-/// 消息的类型
-[SuppressMessage("", "SA1402")]
-internal class DbCurrent
- where TObservable : class
- where TEntity : class, ISelectable
- where TMessage : Message.ValueChangedMessage
-{
- private readonly DbContext dbContext;
- private readonly DbSet dbSet;
- private readonly IMessenger messenger;
- private readonly Func selector;
-
- private TObservable? current;
-
- ///
- /// 构造一个新的数据库当前项
- ///
- /// 数据库上下文
- /// 数据集
- /// 选择器
- /// 消息器
- public DbCurrent(DbContext dbContext, DbSet dbSet, Func selector, IMessenger messenger)
- {
- this.dbContext = dbContext;
- this.dbSet = dbSet;
- this.selector = selector;
- this.messenger = messenger;
- }
-
- ///
- /// 当前选中的项
- ///
- public TObservable? Current
- {
- get => current;
- set
- {
- // prevent useless sets
- if (current == value)
- {
- return;
- }
-
- // only update when not processing a deletion
- if (value != null)
- {
- if (current != null)
- {
- TEntity entity = selector(current);
- entity.IsSelected = false;
- dbSet.Update(entity);
- dbContext.SaveChanges();
- }
- }
-
- TMessage message = (TMessage)Activator.CreateInstance(typeof(TMessage), current, value)!;
- current = value;
-
- if (current != null)
- {
- TEntity entity = selector(current);
- entity.IsSelected = true;
- dbSet.Update(entity);
- dbContext.SaveChanges();
+ dbSet.UpdateAndSave(current);
}
messenger.Send(message);
diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/DbSetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/DbSetExtension.cs
new file mode 100644
index 00000000..aac80a60
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Extension/DbSetExtension.cs
@@ -0,0 +1,66 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+
+namespace Snap.Hutao.Extension;
+
+///
+/// 数据库集合上下文
+///
+public static class DbSetExtension
+{
+ ///
+ /// 获取对应的数据库上下文
+ ///
+ /// 实体类型
+ /// 数据库集
+ /// 对应的数据库上下文
+ public static DbContext Context(this DbSet dbSet)
+ where TEntity : class
+ {
+ return dbSet.GetService().Context;
+ }
+
+ ///
+ /// 获取或添加一个对应的实体
+ ///
+ /// 实体类型
+ /// 数据库集
+ /// 谓词
+ /// 实体工厂
+ /// 是否添加
+ /// 实体
+ public static TEntity SingleOrAdd(this DbSet dbSet, Func predicate, Func entityFactory, out bool added)
+ where TEntity : class
+ {
+ added = false;
+ TEntity? entry = dbSet.SingleOrDefault(predicate);
+
+ if (entry == null)
+ {
+ entry = entityFactory();
+ dbSet.Add(entry);
+ dbSet.Context().SaveChanges();
+
+ added = true;
+ }
+
+ return entry;
+ }
+
+ ///
+ /// 更新并保存
+ ///
+ /// 实体类型
+ /// 数据库集
+ /// 实体
+ /// 影响条数
+ public static int UpdateAndSave(this DbSet dbSet, TEntity entity)
+ where TEntity : class
+ {
+ dbSet.Update(entity);
+ return dbSet.Context().SaveChanges();
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs
index f263ca0d..09b442b6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs
@@ -8,16 +8,7 @@ namespace Snap.Hutao.Message;
///
/// 成就存档切换消息
///
-[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
internal class AchievementArchiveChangedMessage : ValueChangedMessage
{
- ///
- /// 构造一个新的用户切换消息
- ///
- /// 老用户
- /// 新用户
- public AchievementArchiveChangedMessage(AchievementArchive? oldArchive, AchievementArchive? newArchive)
- : base(oldArchive, newArchive)
- {
- }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Message/GachaArchiveChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/GachaArchiveChangedMessage.cs
index 8d94d630..edd62093 100644
--- a/src/Snap.Hutao/Snap.Hutao/Message/GachaArchiveChangedMessage.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Message/GachaArchiveChangedMessage.cs
@@ -8,16 +8,7 @@ namespace Snap.Hutao.Message;
///
/// 祈愿记录存档切换消息
///
-[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
internal class GachaArchiveChangedMessage : ValueChangedMessage
{
- ///
- /// 构造一个新的用户切换消息
- ///
- /// 老用户
- /// 新用户
- public GachaArchiveChangedMessage(GachaArchive? oldArchive, GachaArchive? newArchive)
- : base(oldArchive, newArchive)
- {
- }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs
index 19ffd27d..3bf2443a 100644
--- a/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs
@@ -8,15 +8,7 @@ namespace Snap.Hutao.Message;
///
/// 用户切换消息
///
+[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
internal class UserChangedMessage : ValueChangedMessage
{
- ///
- /// 构造一个新的用户切换消息
- ///
- /// 老用户
- /// 新用户
- public UserChangedMessage(User? oldUser, User? newUser)
- : base(oldUser, newUser)
- {
- }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs
index 4af90ee3..4bd83669 100644
--- a/src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs
@@ -7,9 +7,17 @@ namespace Snap.Hutao.Message;
/// 值变化消息
///
/// 值的类型
+[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
internal abstract class ValueChangedMessage
where TValue : class
{
+ ///
+ /// 动态访问
+ ///
+ public ValueChangedMessage()
+ {
+ }
+
///
/// 构造一个新的值变化消息
///
@@ -24,10 +32,10 @@ internal abstract class ValueChangedMessage
///
/// 旧的值
///
- public TValue? OldValue { get; private set; }
+ public TValue? OldValue { get; set; }
///
/// 新的值
///
- public TValue? NewValue { get; private set; }
+ public TValue? NewValue { get; set; }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/GachaStatistics.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/GachaStatistics.cs
index 9f513d57..aafe0979 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/GachaStatistics.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Gacha/GachaStatistics.cs
@@ -24,7 +24,7 @@ public class GachaStatistics
public TypedWishSummary PermanentWish { get; set; } = default!;
///
- /// 历史
+ /// 历史卡池
///
public List HistoryWishes { get; set; } = default!;
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
index 3fb45900..74d06f99 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs
@@ -12,6 +12,16 @@ namespace Snap.Hutao.Model.Entity;
[Table("settings")]
public class SettingEntry
{
+ ///
+ /// 游戏路径
+ ///
+ public const string GamePath = "GamePath";
+
+ ///
+ /// 空的历史记录卡池是否可见
+ ///
+ public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
+
///
/// 构造一个新的设置入口
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SkillIconConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SkillIconConverter.cs
index 7dc477bd..d12b3321 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SkillIconConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/SkillIconConverter.cs
@@ -13,6 +13,8 @@ internal class SkillIconConverter : ValueConverterBase
private const string SkillUrl = "https://static.snapgenshin.com/Skill/{0}.png";
private const string TalentUrl = "https://static.snapgenshin.com/Talent/{0}.png";
+ private static readonly Uri UIIconNone = new("https://static.snapgenshin.com/Bg/UI_Icon_None.png");
+
///
/// 名称转Uri
///
@@ -20,6 +22,11 @@ internal class SkillIconConverter : ValueConverterBase
/// 链接
public static Uri IconNameToUri(string name)
{
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ return UIIconNone;
+ }
+
if (name.StartsWith("UI_Talent_"))
{
return new Uri(string.Format(TalentUrl, name));
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
index 518e4230..2a21e0bc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
@@ -40,7 +40,7 @@ internal class AchievementService : IAchievementService
this.appDbContext = appDbContext;
this.logger = logger;
- dbCurrent = new(appDbContext, appDbContext.AchievementArchives, messenger);
+ dbCurrent = new(appDbContext.AchievementArchives, messenger);
achievementDbOperation = new(appDbContext);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs
index c91412a4..441cf087 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AppCenter/Model/Log/LogHelper.cs
@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using System.Diagnostics;
+
namespace Snap.Hutao.Service.AppCenter.Model.Log;
[SuppressMessage("", "SA1600")]
@@ -21,7 +23,7 @@ public static class LogHelper
{
Type = exception.GetType().ToString(),
Message = exception.Message,
- StackTrace = exception.ToString(),
+ StackTrace = exception.StackTrace,
};
if (exception is AggregateException aggregateException)
@@ -35,12 +37,21 @@ public static class LogHelper
}
}
}
- else if (exception.InnerException != null)
+
+ if (exception.InnerException != null)
{
current.InnerExceptions ??= new();
current.InnerExceptions.Add(Create(exception.InnerException));
}
+ StackTrace stackTrace = new(exception, true);
+ StackFrame[] frames = stackTrace.GetFrames();
+
+ if (frames.Length > 0 && frames[0].HasNativeImage())
+ {
+
+ }
+
return current;
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs
index 90c5f8d7..61ee8144 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs
@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Microsoft.EntityFrameworkCore;
+using Snap.Hutao.Context.Database;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Entity;
@@ -19,14 +21,17 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
internal class GachaStatisticsFactory : IGachaStatisticsFactory
{
private readonly IMetadataService metadataService;
+ private readonly AppDbContext appDbContext;
///
/// 构造一个新的祈愿统计工厂
///
/// 元数据服务
- public GachaStatisticsFactory(IMetadataService metadataService)
+ /// 数据库上下文
+ public GachaStatisticsFactory(IMetadataService metadataService, AppDbContext appDbContext)
{
this.metadataService = metadataService;
+ this.appDbContext = appDbContext;
}
///
@@ -41,15 +46,29 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
List gachaevents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
List historyWishBuilders = gachaevents.Select(g => new HistoryWishBuilder(g, nameAvatarMap, nameWeaponMap)).ToList();
+ SettingEntry? entry = await appDbContext.Settings
+ .SingleOrDefaultAsync(e => e.Key == SettingEntry.IsEmptyHistoryWishVisible)
+ .ConfigureAwait(false);
+
+ if (entry == null)
+ {
+ entry = new(SettingEntry.IsEmptyHistoryWishVisible, true.ToString());
+ appDbContext.Settings.Add(entry);
+ await appDbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
+
+ bool isEmptyHistoryWishVisible = bool.Parse(entry.Value!);
+
IOrderedEnumerable orderedItems = items.OrderBy(i => i.Id);
- return await Task.Run(() => CreateCore(orderedItems, historyWishBuilders, idAvatarMap, idWeaponMap)).ConfigureAwait(false);
+ return await Task.Run(() => CreateCore(orderedItems, historyWishBuilders, idAvatarMap, idWeaponMap, isEmptyHistoryWishVisible)).ConfigureAwait(false);
}
private static GachaStatistics CreateCore(
IOrderedEnumerable items,
List historyWishBuilders,
Dictionary avatarMap,
- Dictionary weaponMap)
+ Dictionary weaponMap,
+ bool isEmptyHistoryWishVisible)
{
TypedWishSummaryBuilder permanentWishBuilder = new("奔行世间", TypedWishSummaryBuilder.PermanentWish, 90, 10);
TypedWishSummaryBuilder avatarWishBuilder = new("角色活动", TypedWishSummaryBuilder.AvatarEventWish, 90, 10);
@@ -131,6 +150,7 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
{
// history
HistoryWishes = historyWishBuilders
+ .Where(b => isEmptyHistoryWishVisible || (!b.IsEmpty))
.OrderByDescending(builder => builder.From)
.ThenBy(builder => builder.ConfigType, new GachaConfigTypeComparar())
.Select(builder => builder.ToHistoryWish()).ToList(),
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs
index c8e53cfe..acddb2e9 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HistoryWishBuilder.cs
@@ -67,6 +67,14 @@ internal class HistoryWishBuilder
get => gachaEvent.To;
}
+ ///
+ /// 卡池是否为空
+ ///
+ public bool IsEmpty
+ {
+ get => totalCountTracker <= 0;
+ }
+
///
/// 计数五星角色
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs
index f44cc6c6..fef5135c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs
@@ -88,7 +88,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
this.logger = logger;
this.gachaStatisticsFactory = gachaStatisticsFactory;
- dbCurrent = new(appDbContext, appDbContext.GachaArchives, messenger);
+ dbCurrent = new(appDbContext.GachaArchives, messenger);
}
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
index 35af6758..8b29b3c3 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Threading;
+using Snap.Hutao.Extension;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Game.Locator;
@@ -16,8 +17,6 @@ namespace Snap.Hutao.Service.Game;
[Injection(InjectAs.Transient, typeof(IGameService))]
internal class GameService : IGameService
{
- private const string GamePath = "GamePath";
-
private readonly AppDbContext appDbContext;
private readonly IMemoryCache memoryCache;
private readonly IEnumerable gameLocators;
@@ -38,7 +37,7 @@ internal class GameService : IGameService
///
public async ValueTask> GetGamePathAsync()
{
- string key = $"{nameof(GameService)}.Cache.{GamePath}";
+ string key = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}";
if (memoryCache.TryGetValue(key, out object? value))
{
@@ -46,16 +45,11 @@ internal class GameService : IGameService
}
else
{
- SettingEntry? entry = await appDbContext.Settings
- .SingleOrDefaultAsync(e => e.Key == GamePath)
- .ConfigureAwait(false);
+ SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, null), out bool added);
// Cannot find in setting
- if (entry == null)
+ if (added)
{
- // Create new setting
- entry = new(GamePath, null);
-
// Try locate by registry
IGameLocator locator = gameLocators.Single(l => l.Name == nameof(RegistryLauncherLocator));
ValueResult result = await locator.LocateGamePathAsync().ConfigureAwait(false);
@@ -71,7 +65,7 @@ internal class GameService : IGameService
{
// Save result.
entry.Value = result.Value;
- await appDbContext.Settings.AddAsync(entry).ConfigureAwait(false);
+ appDbContext.Settings.Update(entry);
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
}
else
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs
index 616b462f..7d8086c0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs
@@ -29,8 +29,8 @@ internal class RegistryLauncherLocator : IGameLocator
}
else
{
- string path = result.Value;
- string configPath = Path.Combine(path, "config.ini");
+ string? path = Path.GetDirectoryName(result.Value);
+ string configPath = Path.Combine(path!, "config.ini");
string? escapedPath = null;
using (FileStream stream = File.OpenRead(configPath))
{
@@ -40,7 +40,8 @@ internal class RegistryLauncherLocator : IGameLocator
if (escapedPath != null)
{
- return Task.FromResult>(new(true, Unescape(escapedPath)));
+ string gamePath = Path.Combine(Unescape(escapedPath), "YuanShen.exe");
+ return Task.FromResult>(new(true, gamePath));
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
new file mode 100644
index 00000000..5304e4ab
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
@@ -0,0 +1,160 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Binding.Hutao;
+using Snap.Hutao.Model.Metadata.Avatar;
+using Snap.Hutao.Model.Metadata.Weapon;
+using Snap.Hutao.Service.Metadata;
+using Snap.Hutao.Web.Hutao.Model;
+
+namespace Snap.Hutao.Service.Hutao;
+
+///
+/// 胡桃 API 缓存
+///
+[Injection(InjectAs.Singleton, typeof(IHtaoCache))]
+internal class HutaoCache : IHtaoCache
+{
+ private readonly IHutaoService hutaoService;
+ private readonly IMetadataService metadataService;
+
+ private Dictionary? idAvatarExtendedMap;
+
+ ///
+ /// 构造一个新的胡桃 API 缓存
+ ///
+ /// 胡桃服务
+ /// 元数据服务
+ public HutaoCache(IHutaoService hutaoService, IMetadataService metadataService)
+ {
+ this.hutaoService = hutaoService;
+ this.metadataService = metadataService;
+ }
+
+ ///
+ public List? AvatarUsageRanks { get; set; }
+
+ ///
+ public List? AvatarAppearanceRanks { get; set; }
+
+ ///
+ public List? AvatarConstellationInfos { get; set; }
+
+ ///
+ public List? TeamAppearances { get; set; }
+
+ ///
+ public Overview? Overview { get; set; }
+
+ ///
+ public List? AvatarCollocations { get; set; }
+
+ ///
+ public async ValueTask InitializeForDatabaseViewModelAsync()
+ {
+ if (await metadataService.InitializeAsync().ConfigureAwait(false))
+ {
+ Dictionary idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
+
+ Task avatarAppearanceRankTask = AvatarAppearanceRankAsync(idAvatarMap);
+ Task avatarUsageRank = AvatarUsageRanksAsync(idAvatarMap);
+ Task avatarConstellationInfoTask = AvatarConstellationInfosAsync(idAvatarMap);
+ Task teamAppearanceTask = TeamAppearancesAsync(idAvatarMap);
+ Task ovewviewTask = OverviewAsync();
+
+ await Task.WhenAll(
+ avatarAppearanceRankTask,
+ avatarUsageRank,
+ avatarConstellationInfoTask,
+ teamAppearanceTask,
+ ovewviewTask)
+ .ConfigureAwait(false);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ public async ValueTask InitializeForWikiAvatarViewModelAsync()
+ {
+ if (await metadataService.InitializeAsync().ConfigureAwait(false))
+ {
+ Dictionary idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
+ Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
+ Dictionary idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false);
+
+ // AvatarCollocation
+ List avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false);
+ AvatarCollocations = avatarCollocationsRaw.Select(co =>
+ {
+ return new ComplexAvatarCollocation(idAvatarMap[co.AvatarId])
+ {
+ Avatars = co.Avatars.Select(a => new ComplexAvatar(idAvatarMap[a.Item], a.Rate)).ToList(),
+ Weapons = co.Weapons.Select(w => new ComplexWeapon(idWeaponMap[w.Item], w.Rate)).ToList(),
+ ReliquarySets = co.Reliquaries.Select(r => new ComplexReliquarySet(r, idReliquarySetMap)).ToList(),
+ };
+ }).ToList();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private async ValueTask> GetIdAvatarMapExtendedAsync()
+ {
+ if (idAvatarExtendedMap == null)
+ {
+ Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
+ idAvatarExtendedMap = new(idAvatarMap)
+ {
+ [10000005] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerBoy", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE },
+ [10000007] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerGirl", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE },
+ };
+ }
+
+ return idAvatarExtendedMap;
+ }
+
+ private async Task AvatarAppearanceRankAsync(Dictionary idAvatarMap)
+ {
+ List avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false);
+ AvatarAppearanceRanks = avatarAppearanceRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank
+ {
+ Floor = $"第 {rank.Floor} 层",
+ Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new ComplexAvatar(idAvatarMap[rank.Item], rank.Rate)).ToList(),
+ }).ToList();
+ }
+
+ private async Task AvatarUsageRanksAsync(Dictionary idAvatarMap)
+ {
+ List avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false);
+ AvatarUsageRanks = avatarUsageRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank
+ {
+ Floor = $"第 {rank.Floor} 层",
+ Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new ComplexAvatar(idAvatarMap[rank.Item], rank.Rate)).ToList(),
+ }).ToList();
+ }
+
+ private async Task AvatarConstellationInfosAsync(Dictionary idAvatarMap)
+ {
+ List avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false);
+ AvatarConstellationInfos = avatarConstellationInfosRaw.OrderBy(i => i.HoldingRate).Select(info =>
+ {
+ return new ComplexAvatarConstellationInfo(idAvatarMap[info.AvatarId], info.HoldingRate, info.Constellations.Select(x => x.Rate));
+ }).ToList();
+ }
+
+ private async Task TeamAppearancesAsync(Dictionary idAvatarMap)
+ {
+ List teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false);
+ TeamAppearances = teamAppearancesRaw.OrderByDescending(t => t.Floor).Select(team => new ComplexTeamRank(team, idAvatarMap)).ToList();
+ }
+
+ private async Task OverviewAsync()
+ {
+ Overview = await hutaoService.GetOverviewAsync().ConfigureAwait(false);
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs
similarity index 97%
rename from src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs
rename to src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs
index 120c2654..0b2b6578 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/HutaoService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoService.cs
@@ -2,11 +2,10 @@
// Licensed under the MIT license.
using Microsoft.Extensions.Caching.Memory;
-using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Hutao;
using Snap.Hutao.Web.Hutao.Model;
-namespace Snap.Hutao.Service;
+namespace Snap.Hutao.Service.Hutao;
///
/// 胡桃 API 服务
@@ -75,4 +74,4 @@ internal class HutaoService : IHutaoService
T web = await taskFunc(default).ConfigureAwait(false);
return memoryCache.Set(key, web, TimeSpan.FromMinutes(30));
}
-}
\ No newline at end of file
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHtaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHtaoCache.cs
new file mode 100644
index 00000000..d2443b33
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHtaoCache.cs
@@ -0,0 +1,55 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Binding.Hutao;
+using Snap.Hutao.Web.Hutao.Model;
+
+namespace Snap.Hutao.Service.Hutao;
+
+///
+/// 胡桃 API 缓存
+///
+internal interface IHtaoCache
+{
+ ///
+ /// 角色使用率
+ ///
+ List? AvatarUsageRanks { get; set; }
+
+ ///
+ /// 角色上场率
+ ///
+ List? AvatarAppearanceRanks { get; set; }
+
+ ///
+ /// 角色命座信息
+ ///
+ List? AvatarConstellationInfos { get; set; }
+
+ ///
+ /// 队伍出场
+ ///
+ List? TeamAppearances { get; set; }
+
+ ///
+ /// 总览数据
+ ///
+ Overview? Overview { get; set; }
+
+ ///
+ /// 角色搭配
+ ///
+ List? AvatarCollocations { get; set; }
+
+ ///
+ /// 为数据库视图模型初始化
+ ///
+ /// 任务
+ ValueTask InitializeForDatabaseViewModelAsync();
+
+ ///
+ /// 为Wiki角色视图模型初始化
+ ///
+ /// 任务
+ ValueTask InitializeForWikiAvatarViewModelAsync();
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IHutaoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs
similarity index 96%
rename from src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IHutaoService.cs
rename to src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs
index e55c86c9..1618541e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/IHutaoService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoService.cs
@@ -3,7 +3,7 @@
using Snap.Hutao.Web.Hutao.Model;
-namespace Snap.Hutao.Service.Abstraction;
+namespace Snap.Hutao.Service.Hutao;
///
/// 胡桃 API 服务
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
index eed4d38e..87b5511c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs
@@ -73,7 +73,7 @@ internal class UserService : IUserService
}
}
- Message.UserChangedMessage message = new(currentUser, value);
+ Message.UserChangedMessage message = new() { OldValue = currentUser, NewValue = value };
// 当删除到无用户时也能正常反应状态
currentUser = value;
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index 575d5367..7de19a0f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -28,11 +28,7 @@
Snap.Hutao.Program
$(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT
True
-
-
- embedded
-
-
+ true
embedded
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
index c2848949..707e49cb 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
@@ -25,6 +25,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -38,7 +49,7 @@
+ Margin="12,12,0,-12">