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() {