mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
add api cache
This commit is contained in:
@@ -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;
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
internal class DbCurrent<TEntity, TMessage>
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>, new()
|
||||
{
|
||||
private readonly DbContext dbContext;
|
||||
private readonly DbSet<TEntity> dbSet;
|
||||
@@ -25,12 +26,12 @@ internal class DbCurrent<TEntity, TMessage>
|
||||
/// <summary>
|
||||
/// 构造一个新的数据库当前项
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文</param>
|
||||
/// <param name="dbSet">数据集</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public DbCurrent(DbContext dbContext, DbSet<TEntity> dbSet, IMessenger messenger)
|
||||
///
|
||||
public DbCurrent(DbSet<TEntity> dbSet, IMessenger messenger)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
this.dbContext = dbSet.Context();
|
||||
this.dbSet = dbSet;
|
||||
this.messenger = messenger;
|
||||
}
|
||||
@@ -55,96 +56,18 @@ internal class DbCurrent<TEntity, TMessage>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据库当前项
|
||||
/// 简化对数据库中选中项的管理
|
||||
/// </summary>
|
||||
/// <typeparam name="TObservable">绑定类型</typeparam>
|
||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
[SuppressMessage("", "SA1402")]
|
||||
internal class DbCurrent<TObservable, TEntity, TMessage>
|
||||
where TObservable : class
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TObservable>
|
||||
{
|
||||
private readonly DbContext dbContext;
|
||||
private readonly DbSet<TEntity> dbSet;
|
||||
private readonly IMessenger messenger;
|
||||
private readonly Func<TObservable, TEntity> selector;
|
||||
|
||||
private TObservable? current;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的数据库当前项
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文</param>
|
||||
/// <param name="dbSet">数据集</param>
|
||||
/// <param name="selector">选择器</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public DbCurrent(DbContext dbContext, DbSet<TEntity> dbSet, Func<TObservable, TEntity> selector, IMessenger messenger)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
this.dbSet = dbSet;
|
||||
this.selector = selector;
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中的项
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
66
src/Snap.Hutao/Snap.Hutao/Extension/DbSetExtension.cs
Normal file
66
src/Snap.Hutao/Snap.Hutao/Extension/DbSetExtension.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库集合上下文
|
||||
/// </summary>
|
||||
public static class DbSetExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取对应的数据库上下文
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <returns>对应的数据库上下文</returns>
|
||||
public static DbContext Context<TEntity>(this DbSet<TEntity> dbSet)
|
||||
where TEntity : class
|
||||
{
|
||||
return dbSet.GetService<ICurrentDbContext>().Context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或添加一个对应的实体
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="predicate">谓词</param>
|
||||
/// <param name="entityFactory">实体工厂</param>
|
||||
/// <param name="added">是否添加</param>
|
||||
/// <returns>实体</returns>
|
||||
public static TEntity SingleOrAdd<TEntity>(this DbSet<TEntity> dbSet, Func<TEntity, bool> predicate, Func<TEntity> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int UpdateAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Update(entity);
|
||||
return dbSet.Context().SaveChanges();
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,7 @@ namespace Snap.Hutao.Message;
|
||||
/// <summary>
|
||||
/// 成就存档切换消息
|
||||
/// </summary>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
internal class AchievementArchiveChangedMessage : ValueChangedMessage<AchievementArchive>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的用户切换消息
|
||||
/// </summary>
|
||||
/// <param name="oldArchive">老用户</param>
|
||||
/// <param name="newArchive">新用户</param>
|
||||
public AchievementArchiveChangedMessage(AchievementArchive? oldArchive, AchievementArchive? newArchive)
|
||||
: base(oldArchive, newArchive)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,7 @@ namespace Snap.Hutao.Message;
|
||||
/// <summary>
|
||||
/// 祈愿记录存档切换消息
|
||||
/// </summary>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
internal class GachaArchiveChangedMessage : ValueChangedMessage<GachaArchive>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的用户切换消息
|
||||
/// </summary>
|
||||
/// <param name="oldArchive">老用户</param>
|
||||
/// <param name="newArchive">新用户</param>
|
||||
public GachaArchiveChangedMessage(GachaArchive? oldArchive, GachaArchive? newArchive)
|
||||
: base(oldArchive, newArchive)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,7 @@ namespace Snap.Hutao.Message;
|
||||
/// <summary>
|
||||
/// 用户切换消息
|
||||
/// </summary>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
internal class UserChangedMessage : ValueChangedMessage<User>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的用户切换消息
|
||||
/// </summary>
|
||||
/// <param name="oldUser">老用户</param>
|
||||
/// <param name="newUser">新用户</param>
|
||||
public UserChangedMessage(User? oldUser, User? newUser)
|
||||
: base(oldUser, newUser)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,17 @@ namespace Snap.Hutao.Message;
|
||||
/// 值变化消息
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">值的类型</typeparam>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
internal abstract class ValueChangedMessage<TValue>
|
||||
where TValue : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 动态访问
|
||||
/// </summary>
|
||||
public ValueChangedMessage()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的值变化消息
|
||||
/// </summary>
|
||||
@@ -24,10 +32,10 @@ internal abstract class ValueChangedMessage<TValue>
|
||||
/// <summary>
|
||||
/// 旧的值
|
||||
/// </summary>
|
||||
public TValue? OldValue { get; private set; }
|
||||
public TValue? OldValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 新的值
|
||||
/// </summary>
|
||||
public TValue? NewValue { get; private set; }
|
||||
public TValue? NewValue { get; set; }
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class GachaStatistics
|
||||
public TypedWishSummary PermanentWish { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 历史
|
||||
/// 历史卡池
|
||||
/// </summary>
|
||||
public List<HistoryWish> HistoryWishes { get; set; } = default!;
|
||||
|
||||
|
||||
@@ -12,6 +12,16 @@ namespace Snap.Hutao.Model.Entity;
|
||||
[Table("settings")]
|
||||
public class SettingEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏路径
|
||||
/// </summary>
|
||||
public const string GamePath = "GamePath";
|
||||
|
||||
/// <summary>
|
||||
/// 空的历史记录卡池是否可见
|
||||
/// </summary>
|
||||
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的设置入口
|
||||
/// </summary>
|
||||
|
||||
@@ -13,6 +13,8 @@ internal class SkillIconConverter : ValueConverterBase<string, Uri>
|
||||
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");
|
||||
|
||||
/// <summary>
|
||||
/// 名称转Uri
|
||||
/// </summary>
|
||||
@@ -20,6 +22,11 @@ internal class SkillIconConverter : ValueConverterBase<string, Uri>
|
||||
/// <returns>链接</returns>
|
||||
public static Uri IconNameToUri(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return UIIconNone;
|
||||
}
|
||||
|
||||
if (name.StartsWith("UI_Talent_"))
|
||||
{
|
||||
return new Uri(string.Format(TalentUrl, name));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的祈愿统计工厂
|
||||
/// </summary>
|
||||
/// <param name="metadataService">元数据服务</param>
|
||||
public GachaStatisticsFactory(IMetadataService metadataService)
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
public GachaStatisticsFactory(IMetadataService metadataService, AppDbContext appDbContext)
|
||||
{
|
||||
this.metadataService = metadataService;
|
||||
this.appDbContext = appDbContext;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -41,15 +46,29 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
List<GachaEvent> gachaevents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
|
||||
List<HistoryWishBuilder> 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<GachaItem> 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<GachaItem> items,
|
||||
List<HistoryWishBuilder> historyWishBuilders,
|
||||
Dictionary<int, Avatar> avatarMap,
|
||||
Dictionary<int, Weapon> weaponMap)
|
||||
Dictionary<int, Weapon> 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(),
|
||||
|
||||
@@ -67,6 +67,14 @@ internal class HistoryWishBuilder
|
||||
get => gachaEvent.To;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卡池是否为空
|
||||
/// </summary>
|
||||
public bool IsEmpty
|
||||
{
|
||||
get => totalCountTracker <= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计数五星角色
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -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<IGameLocator> gameLocators;
|
||||
@@ -38,7 +37,7 @@ internal class GameService : IGameService
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ValueResult<bool, string>> 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<bool, string> 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
|
||||
|
||||
@@ -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<ValueResult<bool, string>>(new(true, Unescape(escapedPath)));
|
||||
string gamePath = Path.Combine(Unescape(escapedPath), "YuanShen.exe");
|
||||
return Task.FromResult<ValueResult<bool, string>>(new(true, gamePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
160
src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
Normal file
160
src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃 API 缓存
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton, typeof(IHtaoCache))]
|
||||
internal class HutaoCache : IHtaoCache
|
||||
{
|
||||
private readonly IHutaoService hutaoService;
|
||||
private readonly IMetadataService metadataService;
|
||||
|
||||
private Dictionary<int, Avatar>? idAvatarExtendedMap;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃 API 缓存
|
||||
/// </summary>
|
||||
/// <param name="hutaoService">胡桃服务</param>
|
||||
/// <param name="metadataService">元数据服务</param>
|
||||
public HutaoCache(IHutaoService hutaoService, IMetadataService metadataService)
|
||||
{
|
||||
this.hutaoService = hutaoService;
|
||||
this.metadataService = metadataService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<ComplexAvatarRank>? AvatarUsageRanks { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<ComplexAvatarRank>? AvatarAppearanceRanks { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<ComplexAvatarConstellationInfo>? AvatarConstellationInfos { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<ComplexTeamRank>? TeamAppearances { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Overview? Overview { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<ComplexAvatarCollocation>? AvatarCollocations { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForDatabaseViewModelAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
Dictionary<int, Avatar> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForWikiAvatarViewModelAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
Dictionary<int, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
|
||||
Dictionary<int, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, Model.Metadata.Reliquary.ReliquarySet> idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false);
|
||||
|
||||
// AvatarCollocation
|
||||
List<AvatarCollocation> 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<Dictionary<int, Avatar>> GetIdAvatarMapExtendedAsync()
|
||||
{
|
||||
if (idAvatarExtendedMap == null)
|
||||
{
|
||||
Dictionary<int, Avatar> 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<int, Avatar> idAvatarMap)
|
||||
{
|
||||
List<AvatarAppearanceRank> 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<int, Avatar> idAvatarMap)
|
||||
{
|
||||
List<AvatarUsageRank> 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<int, Avatar> idAvatarMap)
|
||||
{
|
||||
List<AvatarConstellationInfo> 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<int, Avatar> idAvatarMap)
|
||||
{
|
||||
List<TeamAppearance> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃 API 服务
|
||||
55
src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHtaoCache.cs
Normal file
55
src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHtaoCache.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃 API 缓存
|
||||
/// </summary>
|
||||
internal interface IHtaoCache
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色使用率
|
||||
/// </summary>
|
||||
List<ComplexAvatarRank>? AvatarUsageRanks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色上场率
|
||||
/// </summary>
|
||||
List<ComplexAvatarRank>? AvatarAppearanceRanks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色命座信息
|
||||
/// </summary>
|
||||
List<ComplexAvatarConstellationInfo>? AvatarConstellationInfos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 队伍出场
|
||||
/// </summary>
|
||||
List<ComplexTeamRank>? TeamAppearances { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总览数据
|
||||
/// </summary>
|
||||
Overview? Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色搭配
|
||||
/// </summary>
|
||||
List<ComplexAvatarCollocation>? AvatarCollocations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 为数据库视图模型初始化
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask<bool> InitializeForDatabaseViewModelAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 为Wiki角色视图模型初始化
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask<bool> InitializeForWikiAvatarViewModelAsync();
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
namespace Snap.Hutao.Service.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃 API 服务
|
||||
@@ -73,7 +73,7 @@ internal class UserService : IUserService
|
||||
}
|
||||
}
|
||||
|
||||
Message.UserChangedMessage message = new(currentUser, value);
|
||||
Message.UserChangedMessage message = new() { OldValue = currentUser, NewValue = value };
|
||||
|
||||
// 当删除到无用户时也能正常反应状态
|
||||
currentUser = value;
|
||||
|
||||
@@ -28,11 +28,7 @@
|
||||
<StartupObject>Snap.Hutao.Program</StartupObject>
|
||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT</DefineConstants>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -25,6 +25,17 @@
|
||||
|
||||
<Grid>
|
||||
<Pivot>
|
||||
<Pivot.RightHeader>
|
||||
<CommandBar>
|
||||
<AppBarButton>
|
||||
<AppBarButton.Flyout>
|
||||
<Flyout>
|
||||
|
||||
</Flyout>
|
||||
</AppBarButton.Flyout>
|
||||
</AppBarButton>
|
||||
</CommandBar>
|
||||
</Pivot.RightHeader>
|
||||
<PivotItem Header="角色使用">
|
||||
<Pivot ItemsSource="{Binding AvatarUsageRanks}">
|
||||
<Pivot.HeaderTemplate>
|
||||
@@ -38,7 +49,7 @@
|
||||
<GridView
|
||||
SelectionMode="None"
|
||||
ItemsSource="{Binding Avatars}"
|
||||
Margin="12,12,0,0">
|
||||
Margin="12,12,0,-12">
|
||||
<GridView.ItemContainerStyle>
|
||||
<Style TargetType="GridViewItem" BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
@@ -78,7 +89,7 @@
|
||||
<GridView
|
||||
SelectionMode="None"
|
||||
ItemsSource="{Binding Avatars}"
|
||||
Margin="12,12,0,0">
|
||||
Margin="12,12,0,-12">
|
||||
<GridView.ItemContainerStyle>
|
||||
<Style TargetType="GridViewItem" BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
@@ -208,7 +219,7 @@
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ScrollViewer Grid.Column="0">
|
||||
<ScrollViewer Grid.Column="0" Margin="0,12,0,0">
|
||||
<ItemsControl ItemsSource="{Binding Up}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
@@ -219,7 +230,7 @@
|
||||
<DataTemplate>
|
||||
<Border
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Margin="12,12,12,0"
|
||||
Margin="12,0,12,12"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<Grid Margin="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -254,7 +265,7 @@
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<ScrollViewer Grid.Column="1">
|
||||
<ScrollViewer Grid.Column="1" Margin="0,12,0,0">
|
||||
<ItemsControl ItemsSource="{Binding Down}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
@@ -265,7 +276,7 @@
|
||||
<DataTemplate>
|
||||
<Border
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Margin="12,12,12,0"
|
||||
Margin="12,0,12,12"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<Grid Margin="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
|
||||
@@ -73,10 +73,28 @@
|
||||
Severity="Informational"
|
||||
Message="都说了没有了"
|
||||
IsOpen="True"
|
||||
CornerRadius="0,0,4,4"/>
|
||||
CornerRadius="0,0,4,4">
|
||||
<InfoBar.ActionButton>
|
||||
<Button HorizontalAlignment="Right" Width="1" Content="没用的按钮"/>
|
||||
</InfoBar.ActionButton>
|
||||
</InfoBar>
|
||||
</sc:SettingExpander>
|
||||
</sc:SettingsGroup>
|
||||
|
||||
<sc:SettingsGroup Header="祈愿记录">
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="无记录的历史祈愿活动"
|
||||
Description="在祈愿记录页面显示或隐藏无记录的历史祈愿活动">
|
||||
<ToggleSwitch
|
||||
Style="{StaticResource ToggleSwitchSettingStyle}"
|
||||
OnContent="显示"
|
||||
OffContent="隐藏"
|
||||
IsOn="{Binding IsEmptyHistoryWishVisible,Mode=TwoWay}"/>
|
||||
</sc:Setting>
|
||||
|
||||
</sc:SettingsGroup>
|
||||
|
||||
<sc:SettingsGroup Header="测试功能">
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
Source="{Binding Selected,Converter={StaticResource AvatarNameCardPicConverter}}"/>
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="0,0,20,16">
|
||||
<StackPanel Margin="0,0,20,16" MaxWidth="800" HorizontalAlignment="Left">
|
||||
<!--简介-->
|
||||
<Grid
|
||||
Margin="16,16,0,16"
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Binding.Hutao;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
@@ -20,8 +15,7 @@ namespace Snap.Hutao.ViewModel;
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class HutaoDatabaseViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
private readonly IHutaoService hutaoService;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IHtaoCache hutaoCache;
|
||||
|
||||
private List<ComplexAvatarRank>? avatarUsageRanks;
|
||||
private List<ComplexAvatarRank>? avatarAppearanceRanks;
|
||||
@@ -31,13 +25,12 @@ internal class HutaoDatabaseViewModel : ObservableObject, ISupportCancellation
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃数据库视图模型
|
||||
/// </summary>
|
||||
/// <param name="hutaoService">胡桃服务</param>
|
||||
/// <param name="hutaoCache">胡桃服务缓存</param>
|
||||
/// <param name="metadataService">元数据服务</param>
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
public HutaoDatabaseViewModel(IHutaoService hutaoService, IMetadataService metadataService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
|
||||
public HutaoDatabaseViewModel(IHtaoCache hutaoCache, IAsyncRelayCommandFactory asyncRelayCommandFactory)
|
||||
{
|
||||
this.hutaoService = hutaoService;
|
||||
this.metadataService = metadataService;
|
||||
this.hutaoCache = hutaoCache;
|
||||
|
||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||
}
|
||||
@@ -72,80 +65,12 @@ internal class HutaoDatabaseViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
if (await hutaoCache.InitializeForDatabaseViewModelAsync().ConfigureAwait(true))
|
||||
{
|
||||
Dictionary<int, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
idAvatarMap = 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 },
|
||||
};
|
||||
|
||||
Dictionary<int, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, Model.Metadata.Reliquary.ReliquarySet> idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false);
|
||||
|
||||
List<ComplexAvatarRank> avatarAppearanceRanksLocal = default!;
|
||||
List<ComplexAvatarRank> avatarUsageRanksLocal = default!;
|
||||
List<ComplexAvatarConstellationInfo> avatarConstellationInfosLocal = default!;
|
||||
List<ComplexTeamRank> teamAppearancesLocal = default!;
|
||||
|
||||
Task avatarAppearanceRankTask = Task.Run(async () =>
|
||||
{
|
||||
// AvatarAppearanceRank
|
||||
List<AvatarAppearanceRank> avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false);
|
||||
avatarAppearanceRanksLocal = 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();
|
||||
});
|
||||
|
||||
Task avatarUsageRank = Task.Run(async () =>
|
||||
{
|
||||
// AvatarUsageRank
|
||||
List<AvatarUsageRank> avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false);
|
||||
avatarUsageRanksLocal = 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();
|
||||
});
|
||||
|
||||
Task avatarConstellationInfoTask = Task.Run(async () =>
|
||||
{
|
||||
// AvatarConstellationInfo
|
||||
List<AvatarConstellationInfo> avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false);
|
||||
avatarConstellationInfosLocal = avatarConstellationInfosRaw.OrderBy(i => i.HoldingRate).Select(info =>
|
||||
{
|
||||
return new ComplexAvatarConstellationInfo(idAvatarMap[info.AvatarId], info.HoldingRate, info.Constellations.Select(x => x.Rate));
|
||||
}).ToList();
|
||||
});
|
||||
|
||||
Task teamAppearanceTask = Task.Run(async () =>
|
||||
{
|
||||
List<TeamAppearance> teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false);
|
||||
teamAppearancesLocal = teamAppearancesRaw.OrderByDescending(t => t.Floor).Select(team => new ComplexTeamRank(team, idAvatarMap)).ToList();
|
||||
});
|
||||
|
||||
await Task.WhenAll(avatarAppearanceRankTask, avatarUsageRank, avatarConstellationInfoTask, teamAppearanceTask).ConfigureAwait(false);
|
||||
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
AvatarAppearanceRanks = avatarAppearanceRanksLocal;
|
||||
AvatarUsageRanks = avatarUsageRanksLocal;
|
||||
AvatarConstellationInfos = avatarConstellationInfosLocal;
|
||||
TeamAppearances = teamAppearancesLocal;
|
||||
|
||||
//// AvatarCollocation
|
||||
//List<AvatarCollocation> avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false);
|
||||
//List<ComplexAvatarCollocation> avatarCollocationsLocal = 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();
|
||||
AvatarAppearanceRanks = hutaoCache.AvatarAppearanceRanks;
|
||||
AvatarUsageRanks = hutaoCache.AvatarUsageRanks;
|
||||
AvatarConstellationInfos = hutaoCache.AvatarConstellationInfos;
|
||||
TeamAppearances = hutaoCache.TeamAppearances;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
@@ -11,13 +14,24 @@ namespace Snap.Hutao.ViewModel;
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class SettingViewModel : ObservableObject
|
||||
{
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly SettingEntry isEmptyHistoryWishVisibleEntry;
|
||||
|
||||
private bool isEmptyHistoryWishVisible;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的测试视图模型
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
/// <param name="experimental">实验性功能</param>
|
||||
public SettingViewModel(ExperimentalFeaturesViewModel experimental)
|
||||
public SettingViewModel(AppDbContext appDbContext, ExperimentalFeaturesViewModel experimental)
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
Experimental = experimental;
|
||||
|
||||
isEmptyHistoryWishVisibleEntry = appDbContext.Settings
|
||||
.SingleOrAdd(e => e.Key == SettingEntry.IsEmptyHistoryWishVisible, () => new(SettingEntry.IsEmptyHistoryWishVisible, true.ToString()), out _);
|
||||
IsEmptyHistoryWishVisible = bool.Parse(isEmptyHistoryWishVisibleEntry.Value!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -28,6 +42,20 @@ internal class SettingViewModel : ObservableObject
|
||||
get => Core.CoreEnvironment.Version.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 空的历史卡池是否可见
|
||||
/// </summary>
|
||||
public bool IsEmptyHistoryWishVisible
|
||||
{
|
||||
get => isEmptyHistoryWishVisible;
|
||||
set
|
||||
{
|
||||
SetProperty(ref isEmptyHistoryWishVisible, value);
|
||||
isEmptyHistoryWishVisibleEntry.Value = value.ToString();
|
||||
appDbContext.Settings.UpdateAndSave(isEmptyHistoryWishVisibleEntry);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实验性功能
|
||||
/// </summary>
|
||||
|
||||
@@ -27,4 +27,19 @@ public class Overview
|
||||
/// 满星数
|
||||
/// </summary>
|
||||
public int SpiralAbyssFullStar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 统计时间
|
||||
/// </summary>
|
||||
public long Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总时间
|
||||
/// </summary>
|
||||
public double TimeTotal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 平均时间
|
||||
/// </summary>
|
||||
public double TimeAverage { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user