diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml
index d9228ac7..44b56ba5 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml
@@ -46,6 +46,9 @@
212
268
268
+
+ 180
+
diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs
index eddfc4b6..1cd88fc9 100644
--- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs
@@ -21,10 +21,9 @@ internal static partial class EnumerableExtension
}
long sum = 0;
- ref int reference = ref MemoryMarshal.GetReference(span);
- for (int i = 0; i < span.Length; i++)
+ foreach (int item in span)
{
- sum += Unsafe.Add(ref reference, i);
+ sum += item;
}
return (double)sum / span.Length;
@@ -78,11 +77,11 @@ internal static partial class EnumerableExtension
public static List SelectList(this List list, Func selector)
{
Span span = CollectionsMarshal.AsSpan(list);
- ref TSource reference = ref MemoryMarshal.GetReference(span);
List results = new(span.Length);
- for (int i = 0; i < span.Length; i++)
+
+ foreach (TSource item in span)
{
- results.Add(selector(Unsafe.Add(ref reference, i)));
+ results.Add(selector(item));
}
return results;
diff --git a/src/Snap.Hutao/Snap.Hutao/IdentityStructs.json b/src/Snap.Hutao/Snap.Hutao/IdentityStructs.json
index e77bcd82..860380c6 100644
--- a/src/Snap.Hutao/Snap.Hutao/IdentityStructs.json
+++ b/src/Snap.Hutao/Snap.Hutao/IdentityStructs.json
@@ -1,47 +1,52 @@
-[
+[
{
"Name": "AvatarId",
"Type": "int",
- "Documentation": "8λ ɫId"
+ "Documentation": "8位 角色Id"
},
{
"Name": "EquipAffixId",
"Type": "int",
- "Documentation": "6λ װId"
+ "Documentation": "6位 装备属性Id"
},
{
"Name": "ExtendedEquipAffixId",
"Type": "int",
- "Documentation": "7λ װId"
+ "Documentation": "7位 装备属性Id"
},
{
"Name": "MaterialId",
"Type": "int",
- "Documentation": "3-6λ Id"
+ "Documentation": "3-6位 材料Id"
},
{
"Name": "MonsterId",
"Type": "int",
- "Documentation": "8λ Id"
+ "Documentation": "8位 怪物Id"
},
{
"Name": "PromoteId",
"Type": "int",
- "Documentation": "1-5λ ɫͻId"
+ "Documentation": "1-5位 角色突破提升Id"
},
{
"Name": "ReliquaryAffixId",
"Type": "int",
- "Documentation": "6λ ʥ︱Id"
+ "Documentation": "6位 圣遗物副词条Id"
},
{
"Name": "ReliquaryMainAffixId",
"Type": "int",
- "Documentation": "5λ ʥId"
+ "Documentation": "5位 圣遗物主属性Id"
},
{
"Name": "WeaponId",
"Type": "int",
- "Documentation": "5λ Id"
+ "Documentation": "5位 武器Id"
+ },
+ {
+ "Name": "AchievementId",
+ "Type": "int",
+ "Documentation": "5位 成就Id"
}
]
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs
index d2c1f7a9..4c3be852 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs
@@ -3,6 +3,7 @@
using Snap.Hutao.Model.InterChange.Achievement;
using Snap.Hutao.Model.Intrinsic;
+using Snap.Hutao.Model.Primitive;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -51,7 +52,7 @@ internal sealed class Achievement : IEquatable
///
/// 状态
///
- public AchievementInfoStatus Status { get; set; }
+ public AchievementStatus Status { get; set; }
///
/// 创建一个新的成就
@@ -59,7 +60,7 @@ internal sealed class Achievement : IEquatable
/// 对应的用户id
/// 成就Id
/// 新创建的成就
- public static Achievement Create(in Guid userId, int id)
+ public static Achievement Create(in Guid userId, in AchievementId id)
{
return new()
{
@@ -83,7 +84,7 @@ internal sealed class Achievement : IEquatable
ArchiveId = userId,
Id = uiaf.Id,
Current = uiaf.Current,
- Status = uiaf.Status, // Hot fix | 1.0.30 | Status not set when create database entity
+ Status = uiaf.Status,
Time = DateTimeOffset.FromUnixTimeSeconds(uiaf.Timestamp).ToLocalTime(),
};
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFItem.cs
index 25b8a61c..bafe336e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFItem.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAFItem.cs
@@ -33,5 +33,5 @@ internal sealed class UIAFItem
/// 完成状态
///
[JsonPropertyName("status")]
- public AchievementInfoStatus Status { get; set; }
+ public AchievementStatus Status { get; set; }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/AchievementInfoStatus.cs b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/AchievementStatus.cs
similarity index 90%
rename from src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/AchievementInfoStatus.cs
rename to src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/AchievementStatus.cs
index 34b84f96..4b949fcc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/AchievementInfoStatus.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/AchievementStatus.cs
@@ -7,12 +7,12 @@ namespace Snap.Hutao.Model.Intrinsic;
/// 成就信息状态
///
[HighQuality]
-internal enum AchievementInfoStatus
+internal enum AchievementStatus
{
///
/// 未识别
///
- UNRECOGNIZED = -1,
+ STATUS_UNRECOGNIZED = -1,
///
/// 非法值
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/Achievement.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/Achievement.cs
index 2d8881d7..57d00466 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/Achievement.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Achievement/Achievement.cs
@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Model.Primitive;
+
namespace Snap.Hutao.Model.Metadata.Achievement;
///
@@ -12,7 +14,7 @@ internal sealed class Achievement
///
/// Id
///
- public int Id { get; set; }
+ public AchievementId Id { get; set; }
///
/// 分类Id
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ParameterDescriptor.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ParameterDescriptor.cs
index 228a4958..53108132 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ParameterDescriptor.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ParameterDescriptor.cs
@@ -51,13 +51,10 @@ internal sealed partial class ParameterDescriptor : ValueConverter GetParameterInfos(List formats, List param)
{
Span span = CollectionsMarshal.AsSpan(formats);
- ref DescFormat reference = ref MemoryMarshal.GetReference(span);
-
List results = new(span.Length);
- for (int index = 0; index < span.Length; index++)
- {
- ref DescFormat descFormat = ref Unsafe.Add(ref reference, index);
+ foreach (DescFormat descFormat in span)
+ {
string format = descFormat.Format;
string resultFormatted = ParamRegex().Replace(format, match => EvaluateMatch(match, param));
results.Add(new ParameterDescription { Description = descFormat.Description, Parameter = resultFormatted });
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
index 8659b181..a3a4da98 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
@@ -1537,7 +1537,16 @@ namespace Snap.Hutao.Resource.Localization {
}
///
- /// 查找类似 据上一个五/四星 的本地化字符串。
+ /// 查找类似 成就统计 的本地化字符串。
+ ///
+ internal static string ViewCardAchievementStatisticsTitle {
+ get {
+ return ResourceManager.GetString("ViewCardAchievementStatisticsTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 保底计数 的本地化字符串。
///
internal static string ViewCardGachaStatisticsTitle {
get {
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
index 3c4d1c49..4157be56 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
@@ -1963,6 +1963,9 @@
设置
- 据上一个五/四星
+ 保底计数
+
+
+ 成就统计
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
index f8e7af88..4f132fdf 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
@@ -6,12 +6,13 @@ using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.ExceptionService;
+using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.InterChange.Achievement;
+using Snap.Hutao.Model.Primitive;
+using Snap.Hutao.ViewModel.Achievement;
using System.Collections.ObjectModel;
-using BindingAchievement = Snap.Hutao.ViewModel.Achievement.AchievementView;
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
-using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
namespace Snap.Hutao.Service.Achievement;
@@ -25,10 +26,10 @@ internal sealed class AchievementService : IAchievementService
{
private readonly AppDbContext appDbContext;
private readonly ILogger logger;
- private readonly DbCurrent dbCurrent;
+ private readonly DbCurrent dbCurrent;
private readonly AchievementDbOperation achievementDbOperation;
- private ObservableCollection? archiveCollection;
+ private ObservableCollection? archiveCollection;
///
/// 构造一个新的成就服务
@@ -46,21 +47,21 @@ internal sealed class AchievementService : IAchievementService
}
///
- public EntityArchive? CurrentArchive
+ public AchievementArchive? CurrentArchive
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
///
- public async Task> GetArchiveCollectionAsync()
+ public async Task> GetArchiveCollectionAsync()
{
await ThreadHelper.SwitchToMainThreadAsync();
return archiveCollection ??= appDbContext.AchievementArchives.AsNoTracking().ToObservableCollection();
}
///
- public async Task RemoveArchiveAsync(EntityArchive archive)
+ public async Task RemoveArchiveAsync(AchievementArchive archive)
{
// Sync cache
await ThreadHelper.SwitchToMainThreadAsync();
@@ -76,7 +77,7 @@ internal sealed class AchievementService : IAchievementService
}
///
- public async Task TryAddArchiveAsync(EntityArchive newArchive)
+ public async Task TryAddArchiveAsync(AchievementArchive newArchive)
{
if (string.IsNullOrWhiteSpace(newArchive.Name))
{
@@ -103,28 +104,25 @@ internal sealed class AchievementService : IAchievementService
}
///
- public List GetAchievements(EntityArchive archive, IList metadata)
+ public List GetAchievements(AchievementArchive archive, List metadata)
{
- Guid archiveId = archive.InnerId;
- List entities = appDbContext.Achievements
- .Where(a => a.ArchiveId == archiveId)
- .ToList();
+ Dictionary entityMap;
+ try
+ {
+ entityMap = appDbContext.Achievements
+ .Where(a => a.ArchiveId == archive.InnerId)
+ .AsEnumerable()
+ .ToDictionary(a => a.Id);
+ }
+ catch (ArgumentException ex)
+ {
+ throw ThrowHelper.UserdataCorrupted(SH.ServiceAchievementUserdataCorruptedInnerIdNotUnique, ex);
+ }
- List results = new();
+ List results = new();
foreach (MetadataAchievement meta in metadata)
{
- EntityAchievement? entity = null;
- try
- {
- entity = entities.SingleOrDefault(e => e.Id == meta.Id);
- }
- catch (InvalidOperationException ex)
- {
- ThrowHelper.UserdataCorrupted(SH.ServiceAchievementUserdataCorruptedInnerIdNotUnique, ex);
- }
-
- entity ??= EntityAchievement.Create(archiveId, meta.Id);
-
+ EntityAchievement? entity = entityMap.GetValueOrDefault(meta.Id) ?? EntityAchievement.Create(archive.InnerId, meta.Id);
results.Add(new(entity, meta));
}
@@ -132,7 +130,40 @@ internal sealed class AchievementService : IAchievementService
}
///
- public async Task ExportToUIAFAsync(EntityArchive archive)
+ public async Task> GetAchievementStatisticsAsync(Dictionary achievementMap)
+ {
+ await ThreadHelper.SwitchToBackgroundAsync();
+
+ List results = new();
+ foreach (AchievementArchive archive in appDbContext.AchievementArchives)
+ {
+ int finished = await appDbContext.Achievements
+ .Where(a => a.ArchiveId == archive.InnerId)
+ .Where(a => (int)a.Status >= (int)Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
+ .CountAsync()
+ .ConfigureAwait(false);
+ int count = achievementMap.Count;
+
+ List achievements = await appDbContext.Achievements
+ .Where(a => a.ArchiveId == archive.InnerId)
+ .OrderByDescending(a => a.Time.ToString())
+ .Take(2)
+ .ToListAsync()
+ .ConfigureAwait(false);
+
+ results.Add(new()
+ {
+ DisplayName = archive.Name,
+ FinishDescription = $"{finished}/{count} - {(double)finished / count:P2}",
+ Achievements = achievements.SelectList(entity => new AchievementView(entity, achievementMap[entity.Id])),
+ });
+ }
+
+ return results;
+ }
+
+ ///
+ public async Task ExportToUIAFAsync(AchievementArchive archive)
{
await ThreadHelper.SwitchToBackgroundAsync();
List list = appDbContext.Achievements
@@ -151,7 +182,7 @@ internal sealed class AchievementService : IAchievementService
}
///
- public async Task ImportFromUIAFAsync(EntityArchive archive, List list, ImportStrategy strategy)
+ public async Task ImportFromUIAFAsync(AchievementArchive archive, List list, ImportStrategy strategy)
{
await ThreadHelper.SwitchToBackgroundAsync();
@@ -185,7 +216,7 @@ internal sealed class AchievementService : IAchievementService
}
///
- public void SaveAchievements(EntityArchive archive, IList achievements)
+ public void SaveAchievements(AchievementArchive archive, List achievements)
{
string name = archive.Name;
logger.LogInformation("Begin saving achievements for [{name}]", name);
@@ -203,7 +234,7 @@ internal sealed class AchievementService : IAchievementService
}
///
- public void SaveAchievement(BindingAchievement achievement)
+ public void SaveAchievement(AchievementView achievement)
{
// Delete exists one.
appDbContext.Achievements.ExecuteDeleteWhere(e => e.InnerId == achievement.Entity.InnerId);
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs
index 3ade48ea..29755581 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.InterChange.Achievement;
+using Snap.Hutao.Model.Primitive;
using System.Collections.ObjectModel;
using BindingAchievement = Snap.Hutao.ViewModel.Achievement.AchievementView;
using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
@@ -33,7 +34,14 @@ internal interface IAchievementService
/// 用户
/// 元数据
/// 整合的成就
- List GetAchievements(EntityArchive archive, IList metadata);
+ List GetAchievements(EntityArchive archive, List metadata);
+
+ ///
+ /// 异步获取成就统计列表
+ ///
+ /// 成就映射
+ /// 成就统计列表
+ Task> GetAchievementStatisticsAsync(Dictionary achievementMap);
///
/// 异步获取用于绑定的成就存档集合
@@ -68,7 +76,7 @@ internal interface IAchievementService
///
/// 用户
/// 成就
- void SaveAchievements(EntityArchive archive, IList achievements);
+ void SaveAchievements(EntityArchive archive, List achievements);
///
/// 尝试添加存档
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs
index af9d3657..a4b54ac7 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs
@@ -34,7 +34,7 @@ internal sealed class SummaryFactory : ISummaryFactory
IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false),
IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false),
IdRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false),
- IdReliquaryAffixMap = await metadataService.GetIdReliquaryAffixMapAsync(token).ConfigureAwait(false),
+ IdReliquaryAffixMap = await metadataService.GetIdToReliquaryAffixMapAsync(token).ConfigureAwait(false),
ReliqueryLevels = await metadataService.GetReliquaryLevelsAsync(token).ConfigureAwait(false),
Reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false),
};
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs
index 69f8c57b..12671d59 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs
@@ -60,6 +60,13 @@ internal interface IMetadataService : ICastableService
/// 卡池配置列表
ValueTask> GetGachaEventsAsync(CancellationToken token = default);
+ ///
+ /// 异步获取成就映射
+ ///
+ /// 取消令牌
+ /// 成就映射
+ ValueTask> GetIdToAchievementMapAsync(CancellationToken token = default);
+
///
/// 异步获取Id到角色的字典
///
@@ -67,6 +74,13 @@ internal interface IMetadataService : ICastableService
/// Id到角色的字典
ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default);
+ ///
+ /// 异步获取显示与材料映射
+ ///
+ /// 取消令牌
+ /// 显示与材料映射
+ ValueTask> GetIdToDisplayAndMaterialMapAsync(CancellationToken token = default);
+
///
/// 异步获取Id到材料的字典
///
@@ -79,7 +93,7 @@ internal interface IMetadataService : ICastableService
///
/// 取消令牌
/// 字典
- ValueTask> GetIdReliquaryAffixMapAsync(CancellationToken token = default);
+ ValueTask> GetIdToReliquaryAffixMapAsync(CancellationToken token = default);
///
/// 异步获取圣遗物主词条Id与属性的字典
@@ -199,11 +213,4 @@ internal interface IMetadataService : ICastableService
/// 取消令牌
/// 武器突破列表
ValueTask> GetWeaponPromotesAsync(CancellationToken token = default);
-
- ///
- /// 异步获取显示与材料映射
- ///
- /// 取消令牌
- /// 显示与材料映射
- ValueTask> GetIdToDisplayAndMaterialMapAsync(CancellationToken token = default);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs
index 8d9f6d83..0ac415ab 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Indexing.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
@@ -22,6 +23,12 @@ internal sealed partial class MetadataService
return FromCacheAsDictionaryAsync("ReliquarySet", r => r.EquipAffixId, token);
}
+ ///
+ public ValueTask> GetIdToAchievementMapAsync(CancellationToken token = default)
+ {
+ return FromCacheAsDictionaryAsync("Achievement", a => a.Id, token);
+ }
+
///
public ValueTask> GetIdToAvatarMapAsync(CancellationToken token = default)
{
@@ -31,8 +38,15 @@ internal sealed partial class MetadataService
///
public async ValueTask> GetIdToDisplayAndMaterialMapAsync(CancellationToken token = default)
{
+ string cacheKey = $"{nameof(MetadataService)}.Cache.DisplayAndMaterial.Map.{typeof(MaterialId).Name}";
+
+ if (memoryCache.TryGetValue(cacheKey, out object? value))
+ {
+ return Must.NotNull((Dictionary)value!);
+ }
+
Dictionary displays = await FromCacheAsDictionaryAsync("Display", a => a.Id, token).ConfigureAwait(false);
- Dictionary materials = await FromCacheAsDictionaryAsync("Material", a => a.Id, token).ConfigureAwait(false);
+ Dictionary materials = await GetIdToMaterialMapAsync(token).ConfigureAwait(false);
// TODO: Cache this
Dictionary results = new(displays);
@@ -42,7 +56,7 @@ internal sealed partial class MetadataService
results[id] = material;
}
- return results;
+ return memoryCache.Set(cacheKey, results);
}
///
@@ -52,7 +66,7 @@ internal sealed partial class MetadataService
}
///
- public ValueTask> GetIdReliquaryAffixMapAsync(CancellationToken token = default)
+ public ValueTask> GetIdToReliquaryAffixMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync("ReliquaryAffix", a => a.Id, token);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index 36cc0658..8b93604a 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -237,7 +237,7 @@
-
+
@@ -255,7 +255,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Card/AchievementCard.xaml b/src/Snap.Hutao/Snap.Hutao/View/Card/AchievementCard.xaml
index 09b0aac9..03e81402 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Card/AchievementCard.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Card/AchievementCard.xaml
@@ -3,9 +3,69 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:local="using:Snap.Hutao.View.Card"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:mxi="using:Microsoft.Xaml.Interactivity"
+ xmlns:shcb="using:Snap.Hutao.Control.Behavior"
+ xmlns:shcm="using:Snap.Hutao.Control.Markup"
+ xmlns:shva="using:Snap.Hutao.ViewModel.Achievement"
+ xmlns:shvc="using:Snap.Hutao.View.Control"
+ Padding="0"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalContentAlignment="Stretch"
+ d:DataContext="{d:DesignInstance shva:AchievementViewModelSlim}"
+ Command="{Binding NavigateCommand}"
+ Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Card/GachaStatisticsCard.xaml b/src/Snap.Hutao/Snap.Hutao/View/Card/GachaStatisticsCard.xaml
index ef0c46a7..84b9442e 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Card/GachaStatisticsCard.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Card/GachaStatisticsCard.xaml
@@ -7,7 +7,7 @@
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
- xmlns:shvco="using:Snap.Hutao.View.Control"
+ xmlns:shvc="using:Snap.Hutao.View.Control"
xmlns:shvg="using:Snap.Hutao.ViewModel.GachaLog"
Padding="0"
HorizontalAlignment="Stretch"
@@ -72,65 +72,67 @@
-
+ Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
+ Text="{Binding LastOrangePull}"/>
-
+ Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
+ Text="{Binding LastPurplePull}"/>
@@ -152,65 +154,67 @@
-
+ Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
+ Text="{Binding LastOrangePull}"/>
-
+ Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
+ Text="{Binding LastPurplePull}"/>
@@ -232,65 +236,67 @@
-
+ Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
+ Text="{Binding LastOrangePull}"/>
-
+ Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
+ Text="{Binding LastPurplePull}"/>
@@ -298,6 +304,6 @@
-
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml
index 405dc406..564713d1 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml
@@ -5,9 +5,13 @@
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ Height="{StaticResource HomeAdaptiveCardHeight}"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ Background="{ThemeResource AccentAcrylicInAppFillColorDefaultBrush}"
mc:Ignorable="d">
-
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
index fea37f07..849c571a 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
@@ -169,24 +169,10 @@
DesiredWidth="300"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
SelectionMode="None">
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementStatistics.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementStatistics.cs
new file mode 100644
index 00000000..bdb76384
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementStatistics.cs
@@ -0,0 +1,25 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.ViewModel.Achievement;
+
+///
+/// 成就统计
+///
+internal sealed class AchievementStatistics
+{
+ ///
+ /// 存档显示名称
+ ///
+ public string DisplayName { get; set; } = default!;
+
+ ///
+ /// 完成进度描述 xxx/yyy
+ ///
+ public string FinishDescription { get; set; } = default!;
+
+ ///
+ /// 近期完成的成就
+ ///
+ public List Achievements { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs
index 1a5d08f5..aebb3ee0 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs
@@ -30,7 +30,7 @@ internal sealed class AchievementView : ObservableObject, IEntityWithMetadata= 2;
+ isChecked = (int)entity.Status >= (int)AchievementStatus.STATUS_FINISHED;
}
///
@@ -56,7 +56,7 @@ internal sealed class AchievementView : ObservableObject, IEntityWithMetadata rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
+ List achievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
- if (TryGetAchievements(archive, rawAchievements, out List? combined))
+ if (TryGetAchievements(archive, achievements, out List? combined))
{
// Assemble achievements on the UI thread.
await ThreadHelper.SwitchToMainThreadAsync();
@@ -436,9 +437,9 @@ internal sealed class AchievementViewModel : Abstraction.ViewModel, INavigationR
if (!string.IsNullOrEmpty(search))
{
- if (search.Length == 5 && int.TryParse(search, out int entityId))
+ if (search.Length == 5 && int.TryParse(search, out int achievementId))
{
- Achievements.Filter = obj => ((AchievementView)obj).Inner.Id == entityId;
+ Achievements.Filter = obj => ((AchievementView)obj).Inner.Id == achievementId;
}
else
{
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModelSlim.cs
index bc0b53d3..e61143cd 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModelSlim.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModelSlim.cs
@@ -1,13 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Model.Primitive;
+using Snap.Hutao.Service.Achievement;
+using Snap.Hutao.Service.Metadata;
+
namespace Snap.Hutao.ViewModel.Achievement;
///
/// 简化的成就视图模型
///
+[Injection(InjectAs.Scoped)]
internal sealed class AchievementViewModelSlim : Abstraction.ViewModelSlim
{
+ private List? statisticsList;
+
///
/// 构造一个新的简化的成就视图模型
///
@@ -16,4 +23,33 @@ internal sealed class AchievementViewModelSlim : Abstraction.ViewModelSlim
+ /// 统计列表
+ ///
+ public List? StatisticsList { get => statisticsList; set => SetProperty(ref statisticsList, value); }
+
+ ///
+ protected override async Task OpenUIAsync()
+ {
+ using (IServiceScope scope = ServiceProvider.CreateScope())
+ {
+ IMetadataService metadataService = scope.ServiceProvider.GetRequiredService();
+
+ if (await metadataService.InitializeAsync().ConfigureAwait(false))
+ {
+ Dictionary achievementMap = await metadataService
+ .GetIdToAchievementMapAsync()
+ .ConfigureAwait(false);
+ List list = await scope.ServiceProvider
+ .GetRequiredService()
+ .GetAchievementStatisticsAsync(achievementMap)
+ .ConfigureAwait(false);
+
+ await ThreadHelper.SwitchToMainThreadAsync();
+ StatisticsList = list;
+ IsInitialized = true;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs
index bb947279..7ab301d2 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AnnouncementViewModel.cs
@@ -27,6 +27,7 @@ internal sealed class AnnouncementViewModel : Abstraction.ViewModel
LaunchGameViewModelSlim = serviceProvider.GetRequiredService();
GachaLogViewModelSlim = serviceProvider.GetRequiredService();
+ AchievementViewModelSlim = serviceProvider.GetRequiredService();
}
///
@@ -44,6 +45,11 @@ internal sealed class AnnouncementViewModel : Abstraction.ViewModel
///
public GachaLog.GachaLogViewModelSlim GachaLogViewModelSlim { get; }
+ ///
+ /// 成就统计视图模型
+ ///
+ public Achievement.AchievementViewModelSlim AchievementViewModelSlim { get; }
+
///
protected override async Task OpenUIAsync()
{