Add Hutao GachaEvent Statistics

This commit is contained in:
DismissedLight
2023-07-10 15:13:41 +08:00
parent 41d7441eb3
commit 3eaaf0e4e2
17 changed files with 363 additions and 32 deletions

View File

@@ -3633,6 +3633,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 统计 的本地化字符串。
/// </summary>
internal static string ViewPageGahcaLogPivotStatistics {
get {
return ResourceManager.GetString("ViewPageGahcaLogPivotStatistics", resourceCulture);
}
}
/// <summary>
/// 查找类似 武器 的本地化字符串。
/// </summary>

View File

@@ -2043,4 +2043,7 @@
<data name="ViewPageGachaLogHutaoCloudDeveloperHint" xml:space="preserve">
<value>已注册为开发者,即使服务过期依旧能使用</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>统计</value>
</data>
</root>

View File

@@ -0,0 +1,80 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Service.GachaLog.Factory;
internal sealed class HutaoStatisticsFactory
{
private readonly Dictionary<AvatarId, Avatar> idAvatarMap;
private readonly Dictionary<WeaponId, Weapon> idWeaponMap;
private readonly GachaEvent avatarEvent;
private readonly GachaEvent avatarEvent2;
private readonly GachaEvent weaponEvent;
public HutaoStatisticsFactory(Dictionary<AvatarId, Avatar> idAvatarMap, Dictionary<WeaponId, Weapon> idWeaponMap, List<GachaEvent> gachaEvents)
{
this.idAvatarMap = idAvatarMap;
this.idWeaponMap = idWeaponMap;
DateTimeOffset now = DateTimeOffset.Now;
avatarEvent = gachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish);
avatarEvent2 = gachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish2);
weaponEvent = gachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.WeaponEventWish);
}
public HutaoStatistics Create(GachaEventStatistics raw)
{
return new()
{
AvatarEvent = CreateWishSummary(avatarEvent, raw.AvatarEvent),
AvatarEvent2 = CreateWishSummary(avatarEvent2, raw.AvatarEvent2),
WeaponWish = CreateWishSummary(weaponEvent, raw.WeaponEvent),
};
}
private HutaoWishSummary CreateWishSummary(GachaEvent gachaEvent, List<ItemCount> items)
{
List<StatisticsItem> orangeItems = new();
List<StatisticsItem> purpleItems = new();
List<StatisticsItem> blueItems = new();
foreach (ref readonly ItemCount item in CollectionsMarshal.AsSpan(items))
{
IStatisticsItemSource source = item.Item.Place() switch
{
8U => idAvatarMap[item.Item],
5U => idWeaponMap[item.Item],
_ => throw Must.NeverHappen("不支持的物品 Id"),
};
StatisticsItem statisticsItem = source.ToStatisticsItem(unchecked((int)item.Count));
List<StatisticsItem> list = statisticsItem.Quality switch
{
QualityType.QUALITY_ORANGE => orangeItems,
QualityType.QUALITY_PURPLE => purpleItems,
QualityType.QUALITY_BLUE => blueItems,
_ => throw Must.NeverHappen("意外的物品等级"),
};
list.Add(statisticsItem);
}
return new()
{
Event = gachaEvent,
OrangeItems = orangeItems.OrderByDescending(i => i.Count).ToList(),
PurpleItems = purpleItems.OrderByDescending(i => i.Count).ToList(),
BlueItems = blueItems.OrderByDescending(i => i.Count).ToList(),
};
}
}

View File

@@ -4,6 +4,12 @@
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao;
using Snap.Hutao.Web.Hutao.GachaLog;
@@ -89,6 +95,29 @@ internal sealed partial class HutaoCloudService : IHutaoCloudService
return await homaGachaLogClient.DeleteGachaItemsAsync(uid, token).ConfigureAwait(false);
}
/// <inheritdoc/>
public async Task<ValueResult<bool, HutaoStatistics>> GetCurrentEventStatisticsAsync(CancellationToken token = default)
{
IMetadataService metadataService = serviceProvider.GetRequiredService<IMetadataService>();
if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
Dictionary<WeaponId, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync(token).ConfigureAwait(false);
Response<GachaEventStatistics> response = await homaGachaLogClient.GetGachaEventStatisticsAsync(token).ConfigureAwait(false);
if (response.IsOk())
{
GachaEventStatistics raw = response.Data;
Factory.HutaoStatisticsFactory factory = new(idAvatarMap, idWeaponMap, gachaEvents);
HutaoStatistics statistics = factory.Create(raw);
return new(true, statistics);
}
}
return new(false, default!);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IEnumerable<GachaItem> QueryArchiveGachaItemsByTypeAndEndId(AppDbContext appDbContext, Model.Entity.GachaArchive gachaArchive, GachaConfigType type, long endId)
{

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Response;
namespace Snap.Hutao.Service.GachaLog;
@@ -19,6 +20,13 @@ internal interface IHutaoCloudService
/// <returns>是否删除成功</returns>
Task<ValueResult<bool, string>> DeleteGachaItemsAsync(string uid, CancellationToken token = default);
/// <summary>
/// 异步获取祈愿统计信息
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>祈愿统计信息</returns>
Task<ValueResult<bool, HutaoStatistics>> GetCurrentEventStatisticsAsync(CancellationToken token = default(CancellationToken));
/// <summary>
/// 异步获取服务器上的 Uid 列表
/// </summary>

View File

@@ -115,6 +115,7 @@
<None Remove="View\Control\BottomTextControl.xaml" />
<None Remove="View\Control\DescParamComboBox.xaml" />
<None Remove="View\Control\Elevation.xaml" />
<None Remove="View\Control\HutaoStatisticsCard.xaml" />
<None Remove="View\Control\ItemIcon.xaml" />
<None Remove="View\Control\LaunchGameResourceExpander.xaml" />
<None Remove="View\Control\LoadingView.xaml" />
@@ -301,6 +302,12 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Page Update="View\Control\HutaoStatisticsCard.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Control\Elevation.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -0,0 +1,79 @@
<UserControl
x:Class="Snap.Hutao.View.Control.HutaoStatisticsCard"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shcp="using:Snap.Hutao.Control.Panel"
xmlns:shvcont="using:Snap.Hutao.View.Control"
xmlns:shvg="using:Snap.Hutao.ViewModel.GachaLog"
d:DataContext="{d:DesignInstance shvg:HutaoWishSummary}"
mc:Ignorable="d">
<UserControl.Resources>
<DataTemplate x:Key="GridTemplate" d:DataType="shvg:StatisticsItem">
<Border
Width="40"
Margin="0,4,4,0"
Style="{StaticResource BorderCardStyle}">
<StackPanel>
<shvcont:ItemIcon
Width="40"
Height="40"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
<TextBlock
HorizontalTextAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Count}"
TextTrimming="None"
TextWrapping="NoWrap"/>
</StackPanel>
</Border>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Margin="0,0,0,0" Style="{StaticResource BorderCardStyle}">
<Grid>
<shcp:AspectRatio TargetHeight="320" TargetWidth="690"/>
<shci:CachedImage Source="{Binding Event.Banner}"/>
</Grid>
</Border>
<ScrollViewer Grid.Row="1">
<StackPanel Margin="0,0,0,16">
<TextBlock
Margin="0,16,0,8"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
<GridView
ItemTemplate="{StaticResource GridTemplate}"
ItemsSource="{Binding OrangeItems}"
SelectionMode="None"/>
<TextBlock
Margin="0,16,0,8"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
<GridView
ItemTemplate="{StaticResource GridTemplate}"
ItemsSource="{Binding PurpleItems}"
SelectionMode="None"/>
<TextBlock
Margin="0,16,0,8"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardBlueText}"/>
<GridView
ItemTemplate="{StaticResource GridTemplate}"
ItemsSource="{Binding BlueItems}"
SelectionMode="None"/>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.View.Control;
/// <summary>
/// 胡桃云祈愿统计卡片
/// </summary>
public sealed partial class HutaoStatisticsCard : UserControl
{
public HutaoStatisticsCard()
{
InitializeComponent();
}
}

View File

@@ -10,8 +10,8 @@
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shcp="using:Snap.Hutao.Control.Panel"
xmlns:shvcont="using:Snap.Hutao.View.Control"
xmlns:shvconv="using:Snap.Hutao.View.Converter"
xmlns:shvcoont="using:Snap.Hutao.View.Control"
xmlns:shvg="using:Snap.Hutao.ViewModel.GachaLog"
d:DataContext="{d:DesignInstance shvg:TypedWishSummary}"
mc:Ignorable="d">
@@ -100,7 +100,7 @@
Style="{StaticResource BorderCardStyle}"
ToolTipService.ToolTip="{Binding TimeFormatted}">
<StackPanel>
<shvcoont:ItemIcon
<shvcont:ItemIcon
Width="40"
Height="40"
Icon="{Binding Icon}"

View File

@@ -582,6 +582,31 @@
</StackPanel>
</ScrollViewer>
</PivotItem>
<PivotItem Header="{shcm:ResourceString Name=ViewPageGahcaLogPivotStatistics}">
<Grid>
<mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding HutaoCloudStatisticsViewModel.OpenUICommand}"/>
</mxi:Interaction.Behaviors>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition Width="auto" MinWidth="16"/>
</Grid.ColumnDefinitions>
<shvc:HutaoStatisticsCard
Grid.Column="0"
Margin="16,16,0,16"
DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent}"/>
<shvc:HutaoStatisticsCard
Grid.Column="1"
Margin="16,16,0,16"
DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent2}"/>
<shvc:HutaoStatisticsCard
Grid.Column="2"
Margin="16,16,0,16"
DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponWish}"/>
</Grid>
</PivotItem>
</Pivot>
</Grid>
<Grid Visibility="{Binding Statistics, Converter={StaticResource EmptyObjectToVisibilityRevertConverter}}">

View File

@@ -10,9 +10,7 @@ namespace Snap.Hutao.ViewModel.Abstraction;
/// <summary>
/// 简化的视图模型抽象类
/// </summary>
/// <typeparam name="TPage">页面类型</typeparam>
internal abstract partial class ViewModelSlim<TPage> : ObservableObject
where TPage : Page
internal abstract partial class ViewModelSlim : ObservableObject
{
private bool isInitialized;
@@ -44,6 +42,24 @@ internal abstract partial class ViewModelSlim<TPage> : ObservableObject
{
return Task.CompletedTask;
}
}
/// <summary>
/// 简化的视图模型抽象类
/// 可导航
/// </summary>
/// <typeparam name="TPage">页面类型</typeparam>
internal abstract partial class ViewModelSlim<TPage> : ViewModelSlim
where TPage : Page
{
/// <summary>
/// 构造一个新的简化的视图模型抽象类
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
public ViewModelSlim(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
/// <summary>
/// 导航到指定的页面类型

View File

@@ -27,6 +27,7 @@ namespace Snap.Hutao.ViewModel.GachaLog;
internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
{
private readonly IContentDialogFactory contentDialogFactory;
private readonly HutaoCloudStatisticsViewModel hutaoCloudStatisticsViewModel;
private readonly HutaoCloudViewModel hutaoCloudViewModel;
private readonly IServiceProvider serviceProvider;
private readonly IGachaLogService gachaLogService;
@@ -86,6 +87,11 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
/// </summary>
public HutaoCloudViewModel HutaoCloudViewModel { get => hutaoCloudViewModel; }
/// <summary>
/// 胡桃云祈愿统计试图
/// </summary>
public HutaoCloudStatisticsViewModel HutaoCloudStatisticsViewModel { get => hutaoCloudStatisticsViewModel; }
/// <inheritdoc/>
protected override async Task OpenUIAsync()
{

View File

@@ -27,25 +27,4 @@ internal sealed class GachaStatisticsSlim
/// 奔行世间
/// </summary>
public TypedWishSummarySlim StandardWish { get; set; } = default!;
}
/// <summary>
/// 简化的祈愿统计
/// </summary>
internal sealed class HutaoStatistics
{
/// <summary>
/// 角色活动
/// </summary>
public TypedWishSummarySlim AvatarEvent { get; set; } = default!;
/// <summary>
/// 角色活动2
/// </summary>
public TypedWishSummarySlim AvatarEvent2 { get; set; } = default!;
/// <summary>
/// 神铸赋形
/// </summary>
public TypedWishSummarySlim WeaponWish { get; set; } = default!;
}

View File

@@ -1,16 +1,41 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Service.GachaLog;
using Snap.Hutao.Service.Hutao;
namespace Snap.Hutao.ViewModel.GachaLog;
/// <summary>
/// 胡桃云服务统计视图模型
/// </summary>
[ConstructorGenerated]
[Injection(InjectAs.Scoped)]
internal sealed partial class HutaoCloudStatisticsViewModel
internal sealed class HutaoCloudStatisticsViewModel : Abstraction.ViewModelSlim
{
private HutaoStatistics? statistics;
}
/// <summary>
/// 构造一个新的胡桃云服务统计视图模型
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
public HutaoCloudStatisticsViewModel(IServiceProvider serviceProvider)
: base(serviceProvider)
{
Options = serviceProvider.GetRequiredService<HutaoUserOptions>();
}
public
public HutaoUserOptions Options { get; }
public HutaoStatistics? Statistics { get => statistics; set => SetProperty(ref statistics, value); }
protected override async Task OpenUIAsync()
{
IHutaoCloudService hutaoCloudService = ServiceProvider.GetRequiredService<IHutaoCloudService>();
(bool isOk, HutaoStatistics statistics) = await hutaoCloudService.GetCurrentEventStatisticsAsync().ConfigureAwait(false);
if (isOk)
{
await ServiceProvider.GetRequiredService<ITaskContext>().SwitchToMainThreadAsync();
Statistics = statistics;
}
}
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.GachaLog;
/// <summary>
/// 胡桃云祈愿统计
/// </summary>
internal sealed class HutaoStatistics
{
/// <summary>
/// 角色活动
/// </summary>
public HutaoWishSummary AvatarEvent { get; set; } = default!;
/// <summary>
/// 角色活动2
/// </summary>
public HutaoWishSummary AvatarEvent2 { get; set; } = default!;
/// <summary>
/// 神铸赋形
/// </summary>
public HutaoWishSummary WeaponWish { get; set; } = default!;
}

View File

@@ -1,9 +1,32 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata;
namespace Snap.Hutao.ViewModel.GachaLog;
/// <summary>
/// 祈愿统计概览
/// </summary>
internal sealed class HutaoWishSummary
{
public GachaEvent
/// <summary>
/// 卡池
/// </summary>
public GachaEvent Event { get; set; } = default!;
/// <summary>
/// 五星物品
/// </summary>
public List<StatisticsItem> OrangeItems { get; set; } = default!;
/// <summary>
/// 四星物品
/// </summary>
public List<StatisticsItem> PurpleItems { get; set; } = default!;
/// <summary>
/// 三星物品
/// </summary>
public List<StatisticsItem> BlueItems { get; set; } = default!;
}

View File

@@ -43,7 +43,7 @@ internal sealed class HomaGachaLogClient
public async Task<Response<GachaEventStatistics>> GetGachaEventStatisticsAsync(CancellationToken token = default)
{
Response<GachaEventStatistics>? resp = await httpClient
.TryCatchGetFromJsonAsync<Response<GachaEventStatistics>>(HutaoEndpoints.GachaLogUids, options, logger, token)
.TryCatchGetFromJsonAsync<Response<GachaEventStatistics>>(HutaoEndpoints.GachaLogStatisticsCurrentEvents, options, logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);