achievement card

This commit is contained in:
Lightczx
2023-04-19 18:22:30 +08:00
parent aee5271a2d
commit 04dae7ccd8
25 changed files with 363 additions and 160 deletions

View File

@@ -46,6 +46,9 @@
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
<x:Double x:Key="CompatSplitViewOpenPaneLength2">268</x:Double>
<GridLength x:Key="CompatGridLength2">268</GridLength>
<x:Double x:Key="HomeAdaptiveCardHeight">180</x:Double>
<!-- Brushes -->
<SolidColorBrush x:Key="AvatarPropertyAddValueBrush" Color="{ThemeResource AvatarPropertyAddValueColor}"/>
<!-- Settings -->

View File

@@ -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<TResult> SelectList<TSource, TResult>(this List<TSource> list, Func<TSource, TResult> selector)
{
Span<TSource> span = CollectionsMarshal.AsSpan(list);
ref TSource reference = ref MemoryMarshal.GetReference(span);
List<TResult> 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;

View File

@@ -1,47 +1,52 @@
[
[
{
"Name": "AvatarId",
"Type": "int",
"Documentation": "8λ <20><>ɫId"
"Documentation": "8位 角色Id"
},
{
"Name": "EquipAffixId",
"Type": "int",
"Documentation": "6λ װ<><D7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Id"
"Documentation": "6位 装备属性Id"
},
{
"Name": "ExtendedEquipAffixId",
"Type": "int",
"Documentation": "7λ װ<><D7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Id"
"Documentation": "7位 装备属性Id"
},
{
"Name": "MaterialId",
"Type": "int",
"Documentation": "3-6λ <20><><EFBFBD><EFBFBD>Id"
"Documentation": "3-6位 材料Id"
},
{
"Name": "MonsterId",
"Type": "int",
"Documentation": "8λ <20><><EFBFBD><EFBFBD>Id"
"Documentation": "8位 怪物Id"
},
{
"Name": "PromoteId",
"Type": "int",
"Documentation": "1-5λ <20><>ɫͻ<C9AB><CDBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Id"
"Documentation": "1-5位 角色突破提升Id"
},
{
"Name": "ReliquaryAffixId",
"Type": "int",
"Documentation": "6λ ʥ<><CAA5><EFBFBD><EFBFBD><EFB8B1><EFBFBD><EFBFBD>Id"
"Documentation": "6位 圣遗物副词条Id"
},
{
"Name": "ReliquaryMainAffixId",
"Type": "int",
"Documentation": "5λ ʥ<><CAA5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Id"
"Documentation": "5位 圣遗物主属性Id"
},
{
"Name": "WeaponId",
"Type": "int",
"Documentation": "5λ <20><><EFBFBD><EFBFBD>Id"
"Documentation": "5位 武器Id"
},
{
"Name": "AchievementId",
"Type": "int",
"Documentation": "5位 成就Id"
}
]

View File

@@ -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<Achievement>
/// <summary>
/// 状态
/// </summary>
public AchievementInfoStatus Status { get; set; }
public AchievementStatus Status { get; set; }
/// <summary>
/// 创建一个新的成就
@@ -59,7 +60,7 @@ internal sealed class Achievement : IEquatable<Achievement>
/// <param name="userId">对应的用户id</param>
/// <param name="id">成就Id</param>
/// <returns>新创建的成就</returns>
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<Achievement>
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(),
};
}

View File

@@ -33,5 +33,5 @@ internal sealed class UIAFItem
/// 完成状态
/// </summary>
[JsonPropertyName("status")]
public AchievementInfoStatus Status { get; set; }
public AchievementStatus Status { get; set; }
}

View File

@@ -7,12 +7,12 @@ namespace Snap.Hutao.Model.Intrinsic;
/// 成就信息状态
/// </summary>
[HighQuality]
internal enum AchievementInfoStatus
internal enum AchievementStatus
{
/// <summary>
/// 未识别
/// </summary>
UNRECOGNIZED = -1,
STATUS_UNRECOGNIZED = -1,
/// <summary>
/// 非法值

View File

@@ -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;
/// <summary>
@@ -12,7 +14,7 @@ internal sealed class Achievement
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
public AchievementId Id { get; set; }
/// <summary>
/// 分类Id

View File

@@ -51,13 +51,10 @@ internal sealed partial class ParameterDescriptor : ValueConverter<DescriptionsP
private static List<ParameterDescription> GetParameterInfos(List<DescFormat> formats, List<double> param)
{
Span<DescFormat> span = CollectionsMarshal.AsSpan(formats);
ref DescFormat reference = ref MemoryMarshal.GetReference(span);
List<ParameterDescription> 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 });

View File

@@ -1537,7 +1537,16 @@ namespace Snap.Hutao.Resource.Localization {
}
/// <summary>
/// 查找类似 据上一个五/四星 的本地化字符串。
/// 查找类似 成就统计 的本地化字符串。
/// </summary>
internal static string ViewCardAchievementStatisticsTitle {
get {
return ResourceManager.GetString("ViewCardAchievementStatisticsTitle", resourceCulture);
}
}
/// <summary>
/// 查找类似 保底计数 的本地化字符串。
/// </summary>
internal static string ViewCardGachaStatisticsTitle {
get {

View File

@@ -1963,6 +1963,9 @@
<value>设置</value>
</data>
<data name="ViewCardGachaStatisticsTitle" xml:space="preserve">
<value>据上一个五/四星</value>
<value>保底计数</value>
</data>
<data name="ViewCardAchievementStatisticsTitle" xml:space="preserve">
<value>成就统计</value>
</data>
</root>

View File

@@ -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<AchievementService> logger;
private readonly DbCurrent<EntityArchive, Message.AchievementArchiveChangedMessage> dbCurrent;
private readonly DbCurrent<AchievementArchive, Message.AchievementArchiveChangedMessage> dbCurrent;
private readonly AchievementDbOperation achievementDbOperation;
private ObservableCollection<EntityArchive>? archiveCollection;
private ObservableCollection<AchievementArchive>? archiveCollection;
/// <summary>
/// 构造一个新的成就服务
@@ -46,21 +47,21 @@ internal sealed class AchievementService : IAchievementService
}
/// <inheritdoc/>
public EntityArchive? CurrentArchive
public AchievementArchive? CurrentArchive
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
/// <inheritdoc/>
public async Task<ObservableCollection<EntityArchive>> GetArchiveCollectionAsync()
public async Task<ObservableCollection<AchievementArchive>> GetArchiveCollectionAsync()
{
await ThreadHelper.SwitchToMainThreadAsync();
return archiveCollection ??= appDbContext.AchievementArchives.AsNoTracking().ToObservableCollection();
}
/// <inheritdoc/>
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
}
/// <inheritdoc/>
public async Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive)
public async Task<ArchiveAddResult> TryAddArchiveAsync(AchievementArchive newArchive)
{
if (string.IsNullOrWhiteSpace(newArchive.Name))
{
@@ -103,28 +104,25 @@ internal sealed class AchievementService : IAchievementService
}
/// <inheritdoc/>
public List<BindingAchievement> GetAchievements(EntityArchive archive, IList<MetadataAchievement> metadata)
public List<AchievementView> GetAchievements(AchievementArchive archive, List<MetadataAchievement> metadata)
{
Guid archiveId = archive.InnerId;
List<EntityAchievement> entities = appDbContext.Achievements
.Where(a => a.ArchiveId == archiveId)
.ToList();
Dictionary<int, EntityAchievement> 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<BindingAchievement> results = new();
List<AchievementView> 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
}
/// <inheritdoc/>
public async Task<UIAF> ExportToUIAFAsync(EntityArchive archive)
public async Task<List<AchievementStatistics>> GetAchievementStatisticsAsync(Dictionary<AchievementId, MetadataAchievement> achievementMap)
{
await ThreadHelper.SwitchToBackgroundAsync();
List<AchievementStatistics> 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<EntityAchievement> 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;
}
/// <inheritdoc/>
public async Task<UIAF> ExportToUIAFAsync(AchievementArchive archive)
{
await ThreadHelper.SwitchToBackgroundAsync();
List<UIAFItem> list = appDbContext.Achievements
@@ -151,7 +182,7 @@ internal sealed class AchievementService : IAchievementService
}
/// <inheritdoc/>
public async Task<ImportResult> ImportFromUIAFAsync(EntityArchive archive, List<UIAFItem> list, ImportStrategy strategy)
public async Task<ImportResult> ImportFromUIAFAsync(AchievementArchive archive, List<UIAFItem> list, ImportStrategy strategy)
{
await ThreadHelper.SwitchToBackgroundAsync();
@@ -185,7 +216,7 @@ internal sealed class AchievementService : IAchievementService
}
/// <inheritdoc/>
public void SaveAchievements(EntityArchive archive, IList<BindingAchievement> achievements)
public void SaveAchievements(AchievementArchive archive, List<AchievementView> achievements)
{
string name = archive.Name;
logger.LogInformation("Begin saving achievements for [{name}]", name);
@@ -203,7 +234,7 @@ internal sealed class AchievementService : IAchievementService
}
/// <inheritdoc/>
public void SaveAchievement(BindingAchievement achievement)
public void SaveAchievement(AchievementView achievement)
{
// Delete exists one.
appDbContext.Achievements.ExecuteDeleteWhere(e => e.InnerId == achievement.Entity.InnerId);

View File

@@ -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
/// <param name="archive">用户</param>
/// <param name="metadata">元数据</param>
/// <returns>整合的成就</returns>
List<BindingAchievement> GetAchievements(EntityArchive archive, IList<MetadataAchievement> metadata);
List<BindingAchievement> GetAchievements(EntityArchive archive, List<MetadataAchievement> metadata);
/// <summary>
/// 异步获取成就统计列表
/// </summary>
/// <param name="achievementMap">成就映射</param>
/// <returns>成就统计列表</returns>
Task<List<ViewModel.Achievement.AchievementStatistics>> GetAchievementStatisticsAsync(Dictionary<AchievementId, MetadataAchievement> achievementMap);
/// <summary>
/// 异步获取用于绑定的成就存档集合
@@ -68,7 +76,7 @@ internal interface IAchievementService
/// </summary>
/// <param name="archive">用户</param>
/// <param name="achievements">成就</param>
void SaveAchievements(EntityArchive archive, IList<BindingAchievement> achievements);
void SaveAchievements(EntityArchive archive, List<BindingAchievement> achievements);
/// <summary>
/// 尝试添加存档

View File

@@ -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),
};

View File

@@ -60,6 +60,13 @@ internal interface IMetadataService : ICastableService
/// <returns>卡池配置列表</returns>
ValueTask<List<GachaEvent>> GetGachaEventsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取成就映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>成就映射</returns>
ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取Id到角色的字典
/// </summary>
@@ -67,6 +74,13 @@ internal interface IMetadataService : ICastableService
/// <returns>Id到角色的字典</returns>
ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取显示与材料映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>显示与材料映射</returns>
ValueTask<Dictionary<MaterialId, Display>> GetIdToDisplayAndMaterialMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取Id到材料的字典
/// </summary>
@@ -79,7 +93,7 @@ internal interface IMetadataService : ICastableService
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>字典</returns>
ValueTask<Dictionary<ReliquaryAffixId, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default);
ValueTask<Dictionary<ReliquaryAffixId, ReliquaryAffix>> GetIdToReliquaryAffixMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物主词条Id与属性的字典
@@ -199,11 +213,4 @@ internal interface IMetadataService : ICastableService
/// <param name="token">取消令牌</param>
/// <returns>武器突破列表</returns>
ValueTask<List<Promote>> GetWeaponPromotesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取显示与材料映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>显示与材料映射</returns>
ValueTask<Dictionary<MaterialId, Display>> GetIdToDisplayAndMaterialMapAsync(CancellationToken token = default);
}

View File

@@ -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<EquipAffixId, ReliquarySet>("ReliquarySet", r => r.EquipAffixId, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<AchievementId, Model.Metadata.Achievement.Achievement>("Achievement", a => a.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default)
{
@@ -31,8 +38,15 @@ internal sealed partial class MetadataService
/// <inheritdoc/>
public async ValueTask<Dictionary<MaterialId, Display>> 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<MaterialId, Display>)value!);
}
Dictionary<MaterialId, Display> displays = await FromCacheAsDictionaryAsync<MaterialId, Display>("Display", a => a.Id, token).ConfigureAwait(false);
Dictionary<MaterialId, Material> materials = await FromCacheAsDictionaryAsync<MaterialId, Material>("Material", a => a.Id, token).ConfigureAwait(false);
Dictionary<MaterialId, Material> materials = await GetIdToMaterialMapAsync(token).ConfigureAwait(false);
// TODO: Cache this
Dictionary<MaterialId, Display> results = new(displays);
@@ -42,7 +56,7 @@ internal sealed partial class MetadataService
results[id] = material;
}
return results;
return memoryCache.Set(cacheKey, results);
}
/// <inheritdoc/>
@@ -52,7 +66,7 @@ internal sealed partial class MetadataService
}
/// <inheritdoc/>
public ValueTask<Dictionary<ReliquaryAffixId, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default)
public ValueTask<Dictionary<ReliquaryAffixId, ReliquaryAffix>> GetIdToReliquaryAffixMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<ReliquaryAffixId, ReliquaryAffix>("ReliquaryAffix", a => a.Id, token);
}

View File

@@ -237,7 +237,7 @@
<!-- Packages -->
<ItemGroup>
<!-- https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json -->
<PackageReference Include="CommunityToolkit.Labs.WinUI.SettingsControls" Version="0.0.17" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.SettingsControls" Version="0.0.18" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
@@ -255,7 +255,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.6.11" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.206-beta">
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.221-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -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">
<mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
</mxi:Interaction.Behaviors>
<Grid>
<FlipView
Background="{x:Null}"
ItemsSource="{Binding StatisticsList}"
Visibility="{Binding IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}">
<FlipView.ItemTemplate>
<DataTemplate>
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{shcm:ResourceString Name=ViewCardAchievementStatisticsTitle}"/>
<TextBlock
Grid.Row="0"
HorizontalAlignment="Right"
Text="{Binding DisplayName}"/>
<TextBlock
Grid.Row="1"
Margin="0,4,0,0"
Style="{StaticResource TitleTextBlockStyle}"
Text="{Binding FinishDescription}"/>
<ItemsControl Grid.Row="2" ItemsSource="{Binding Achievements}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,4,0,0">
<TextBlock
Opacity="0.8"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{Binding Inner.Title}"
TextTrimming="CharacterEllipsis"/>
<TextBlock
Opacity="0.6"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Time}"
TextWrapping="NoWrap"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
<Grid/>
</FlipView>
<shvc:LoadingViewSlim IsLoading="{Binding IsInitialized, Converter={StaticResource BoolNegationConverter}}"/>
</Grid>
</Button>

View File

@@ -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 @@
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="32"
Height="32"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
<ProgressBar
Grid.ColumnSpan="2"
MinHeight="40"
Background="Transparent"
CornerRadius="{StaticResource CompatCornerRadius}"
Foreground="{StaticResource OrangeBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteeOrangeThreshold}"
Opacity="0.15"
Style="{StaticResource DefaultProgressBarStyle}"
Value="{Binding LastOrangePull}"/>
<TextBlock
Grid.Column="0"
Margin="12,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding LastOrangePull}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
Text="{Binding LastOrangePull}"/>
</Grid>
</Border>
<Border
Grid.Column="1"
Margin="0,6,0,0"
Margin="0,3,0,0"
Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="32"
Height="32"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
<ProgressBar
Grid.ColumnSpan="2"
MinHeight="40"
Background="Transparent"
CornerRadius="{StaticResource CompatCornerRadius}"
Foreground="{StaticResource PurpleBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteePurpleThreshold}"
Opacity="0.15"
Style="{StaticResource DefaultProgressBarStyle}"
Value="{Binding LastPurplePull}"/>
<TextBlock
Grid.Column="0"
Margin="12,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding LastPurplePull}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
Text="{Binding LastPurplePull}"/>
</Grid>
</Border>
</StackPanel>
@@ -152,65 +154,67 @@
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="32"
Height="32"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
<ProgressBar
Grid.ColumnSpan="2"
MinHeight="40"
Background="Transparent"
CornerRadius="{StaticResource CompatCornerRadius}"
Foreground="{StaticResource OrangeBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteeOrangeThreshold}"
Opacity="0.15"
Style="{StaticResource DefaultProgressBarStyle}"
Value="{Binding LastOrangePull}"/>
<TextBlock
Grid.Column="0"
Margin="12,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding LastOrangePull}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
Text="{Binding LastOrangePull}"/>
</Grid>
</Border>
<Border
Grid.Column="1"
Margin="0,6,0,0"
Margin="0,3,0,0"
Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="32"
Height="32"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
<ProgressBar
Grid.ColumnSpan="2"
MinHeight="40"
Background="Transparent"
CornerRadius="{StaticResource CompatCornerRadius}"
Foreground="{StaticResource PurpleBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteePurpleThreshold}"
Opacity="0.15"
Style="{StaticResource DefaultProgressBarStyle}"
Value="{Binding LastPurplePull}"/>
<TextBlock
Grid.Column="0"
Margin="12,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding LastPurplePull}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
Text="{Binding LastPurplePull}"/>
</Grid>
</Border>
</StackPanel>
@@ -232,65 +236,67 @@
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="32"
Height="32"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
<ProgressBar
Grid.ColumnSpan="2"
MinHeight="40"
Background="Transparent"
CornerRadius="{StaticResource CompatCornerRadius}"
Foreground="{StaticResource OrangeBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteeOrangeThreshold}"
Opacity="0.15"
Style="{StaticResource DefaultProgressBarStyle}"
Value="{Binding LastOrangePull}"/>
<TextBlock
Grid.Column="0"
Margin="12,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding LastOrangePull}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
Text="{Binding LastOrangePull}"/>
</Grid>
</Border>
<Border
Grid.Column="1"
Margin="0,6,0,0"
Margin="0,3,0,0"
Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="32"
Height="32"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
<ProgressBar
Grid.ColumnSpan="2"
MinHeight="40"
Background="Transparent"
CornerRadius="{StaticResource CompatCornerRadius}"
Foreground="{StaticResource PurpleBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteePurpleThreshold}"
Opacity="0.15"
Style="{StaticResource DefaultProgressBarStyle}"
Value="{Binding LastPurplePull}"/>
<TextBlock
Grid.Column="0"
Margin="12,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding LastPurplePull}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
Text="{Binding LastPurplePull}"/>
</Grid>
</Border>
</StackPanel>
@@ -298,6 +304,6 @@
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
<shvco:LoadingViewSlim IsLoading="{Binding IsInitialized, Converter={StaticResource BoolNegationConverter}}"/>
<shvc:LoadingViewSlim IsLoading="{Binding IsInitialized, Converter={StaticResource BoolNegationConverter}}"/>
</Grid>
</Button>

View File

@@ -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">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ProgressRing IsActive="True"/>
</StackPanel>
</cwuc:Loading>

View File

@@ -169,24 +169,10 @@
DesiredWidth="300"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
SelectionMode="None">
<shvca:LaunchGameCard Height="166" DataContext="{Binding LaunchGameViewModelSlim}"/>
<shvca:LaunchGameCard Height="{StaticResource HomeAdaptiveCardHeight}" DataContext="{Binding LaunchGameViewModelSlim}"/>
<shvca:GachaStatisticsCard DataContext="{Binding GachaLogViewModelSlim}"/>
<shvca:AchievementCard DataContext="{Binding AchievementViewModelSlim}"/>
<Border Style="{StaticResource BorderCardStyle}">
<FlipView Background="{x:Null}">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="100/800"/>
<TextBlock Text="12.5%"/>
</StackPanel>
<TextBlock Grid.Row="1" Text="Archive Name"/>
</Grid>
</FlipView>
</Border>
<Border Style="{StaticResource BorderCardStyle}">
<TextBlock Text="实时便笺"/>
</Border>

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.Achievement;
/// <summary>
/// 成就统计
/// </summary>
internal sealed class AchievementStatistics
{
/// <summary>
/// 存档显示名称
/// </summary>
public string DisplayName { get; set; } = default!;
/// <summary>
/// 完成进度描述 xxx/yyy
/// </summary>
public string FinishDescription { get; set; } = default!;
/// <summary>
/// 近期完成的成就
/// </summary>
public List<AchievementView> Achievements { get; set; } = default!;
}

View File

@@ -30,7 +30,7 @@ internal sealed class AchievementView : ObservableObject, IEntityWithMetadata<Mo
Entity = entity;
Inner = inner;
isChecked = (int)entity.Status >= 2;
isChecked = (int)entity.Status >= (int)AchievementStatus.STATUS_FINISHED;
}
/// <summary>
@@ -56,7 +56,7 @@ internal sealed class AchievementView : ObservableObject, IEntityWithMetadata<Mo
// Only update state when checked
if (value)
{
Entity.Status = AchievementInfoStatus.STATUS_REWARD_TAKEN;
Entity.Status = AchievementStatus.STATUS_REWARD_TAKEN;
Entity.Time = DateTimeOffset.Now;
OnPropertyChanged(nameof(Time));
}

View File

@@ -9,6 +9,7 @@ using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.InterChange.Achievement;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Achievement;
using Snap.Hutao.Service.Metadata;
@@ -367,9 +368,9 @@ internal sealed class AchievementViewModel : Abstraction.ViewModel, INavigationR
return;
}
List<MetadataAchievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List<MetadataAchievement> achievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
if (TryGetAchievements(archive, rawAchievements, out List<AchievementView>? combined))
if (TryGetAchievements(archive, achievements, out List<AchievementView>? 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
{

View File

@@ -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;
/// <summary>
/// 简化的成就视图模型
/// </summary>
[Injection(InjectAs.Scoped)]
internal sealed class AchievementViewModelSlim : Abstraction.ViewModelSlim<View.Page.AchievementPage>
{
private List<AchievementStatistics>? statisticsList;
/// <summary>
/// 构造一个新的简化的成就视图模型
/// </summary>
@@ -16,4 +23,33 @@ internal sealed class AchievementViewModelSlim : Abstraction.ViewModelSlim<View.
: base(serviceProvider)
{
}
/// <summary>
/// 统计列表
/// </summary>
public List<AchievementStatistics>? StatisticsList { get => statisticsList; set => SetProperty(ref statisticsList, value); }
/// <inheritdoc/>
protected override async Task OpenUIAsync()
{
using (IServiceScope scope = ServiceProvider.CreateScope())
{
IMetadataService metadataService = scope.ServiceProvider.GetRequiredService<IMetadataService>();
if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
Dictionary<AchievementId, Model.Metadata.Achievement.Achievement> achievementMap = await metadataService
.GetIdToAchievementMapAsync()
.ConfigureAwait(false);
List<AchievementStatistics> list = await scope.ServiceProvider
.GetRequiredService<IAchievementService>()
.GetAchievementStatisticsAsync(achievementMap)
.ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
StatisticsList = list;
IsInitialized = true;
}
}
}
}

View File

@@ -27,6 +27,7 @@ internal sealed class AnnouncementViewModel : Abstraction.ViewModel
LaunchGameViewModelSlim = serviceProvider.GetRequiredService<Game.LaunchGameViewModelSlim>();
GachaLogViewModelSlim = serviceProvider.GetRequiredService<GachaLog.GachaLogViewModelSlim>();
AchievementViewModelSlim = serviceProvider.GetRequiredService<Achievement.AchievementViewModelSlim>();
}
/// <summary>
@@ -44,6 +45,11 @@ internal sealed class AnnouncementViewModel : Abstraction.ViewModel
/// </summary>
public GachaLog.GachaLogViewModelSlim GachaLogViewModelSlim { get; }
/// <summary>
/// 成就统计视图模型
/// </summary>
public Achievement.AchievementViewModelSlim AchievementViewModelSlim { get; }
/// <inheritdoc/>
protected override async Task OpenUIAsync()
{