add refresh time for avatar property

This commit is contained in:
DismissedLight
2023-08-27 17:02:23 +08:00
parent 00f083608e
commit 8ca6251d33
14 changed files with 169 additions and 42 deletions

View File

@@ -48,7 +48,7 @@
<GridLength x:Key="CompatGridLength2">288</GridLength>
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
<x:Double x:Key="CompatSplitViewOpenPaneLength2">304</x:Double>
<x:Double x:Key="CompatSplitViewOpenPaneLength3">320</x:Double>
<x:Double x:Key="HomeAdaptiveCardHeight">180</x:Double>
<x:Double x:Key="ContentDialogMinHeight">64</x:Double>
<x:Double x:Key="LargeAppBarButtonWidth">100</x:Double>

View File

@@ -9,6 +9,8 @@ namespace Snap.Hutao.Extension;
[HighQuality]
internal static class DateTimeOffsetExtension
{
public static readonly DateTimeOffset DatebaseDefaultTime = new(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0));
/// <summary>
/// 从Unix时间戳转换
/// </summary>

View File

@@ -58,7 +58,15 @@ internal sealed class DailyNoteEntry : ObservableObject, IMappingFrom<DailyNoteE
public DateTimeOffset RefreshTime { get; set; }
[NotMapped]
public string RefreshTimeFormatted { get => SH.ModelEntityDailyNoteRefreshTimeFormat.Format(RefreshTime); }
public string RefreshTimeFormatted
{
get
{
return RefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
? SH.ModelEntityDailyNoteNotRefreshed
: SH.ModelEntityDailyNoteRefreshTimeFormat.Format(RefreshTime);
}
}
/// <summary>
/// 树脂提醒阈值

View File

@@ -420,6 +420,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 尚未刷新 的本地化字符串。
/// </summary>
internal static string ModelEntityDailyNoteNotRefreshed {
get {
return ResourceManager.GetString("ModelEntityDailyNoteNotRefreshed", resourceCulture);
}
}
/// <summary>
/// 查找类似 刷新于 {0:yyyy/MM/dd HH:mm:ss} 的本地化字符串。
/// </summary>
@@ -1122,6 +1131,60 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 养成计算:尚未刷新 的本地化字符串。
/// </summary>
internal static string ServiceAvatarInfoSummaryCalculatorNotRefreshed {
get {
return ResourceManager.GetString("ServiceAvatarInfoSummaryCalculatorNotRefreshed", resourceCulture);
}
}
/// <summary>
/// 查找类似 养成计算:{0:MM-dd HH:mm} 的本地化字符串。
/// </summary>
internal static string ServiceAvatarInfoSummaryCalculatorRefreshTimeFormat {
get {
return ResourceManager.GetString("ServiceAvatarInfoSummaryCalculatorRefreshTimeFormat", resourceCulture);
}
}
/// <summary>
/// 查找类似 我的角色:尚未刷新 的本地化字符串。
/// </summary>
internal static string ServiceAvatarInfoSummaryGameRecordNotRefreshed {
get {
return ResourceManager.GetString("ServiceAvatarInfoSummaryGameRecordNotRefreshed", resourceCulture);
}
}
/// <summary>
/// 查找类似 我的角色:{0:MM-dd HH:mm} 的本地化字符串。
/// </summary>
internal static string ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat {
get {
return ResourceManager.GetString("ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat", resourceCulture);
}
}
/// <summary>
/// 查找类似 角色橱窗:尚未刷新 的本地化字符串。
/// </summary>
internal static string ServiceAvatarInfoSummaryShowcaseNotRefreshed {
get {
return ResourceManager.GetString("ServiceAvatarInfoSummaryShowcaseNotRefreshed", resourceCulture);
}
}
/// <summary>
/// 查找类似 角色橱窗:{0:MM-dd HH:mm} 的本地化字符串。
/// </summary>
internal static string ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat {
get {
return ResourceManager.GetString("ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat", resourceCulture);
}
}
/// <summary>
/// 查找类似 保存养成计划状态失败 的本地化字符串。
/// </summary>

View File

@@ -237,6 +237,9 @@
<data name="ModelBindingUserInitializationFailed" xml:space="preserve">
<value>网络异常</value>
</data>
<data name="ModelEntityDailyNoteNotRefreshed" xml:space="preserve">
<value>尚未刷新</value>
</data>
<data name="ModelEntityDailyNoteRefreshTimeFormat" xml:space="preserve">
<value>刷新于 {0:yyyy/MM/dd HH:mm:ss}</value>
</data>
@@ -527,6 +530,24 @@
<value>风元素抗性</value>
<comment>Need EXACT same string in game</comment>
</data>
<data name="ServiceAvatarInfoSummaryCalculatorNotRefreshed" xml:space="preserve">
<value>养成计算:尚未刷新</value>
</data>
<data name="ServiceAvatarInfoSummaryCalculatorRefreshTimeFormat" xml:space="preserve">
<value>养成计算:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
<value>我的角色:尚未刷新</value>
</data>
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
<value>我的角色:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
<value>角色橱窗:尚未刷新</value>
</data>
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色橱窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>保存养成计划状态失败</value>
</data>

View File

@@ -40,7 +40,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
/// <param name="webInfos">Enka信息</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
public List<EnkaAvatarInfo> UpdateDbAvatarInfosByShowcase(string uid, IEnumerable<EnkaAvatarInfo> webInfos, CancellationToken token)
public List<EntityAvatarInfo> UpdateDbAvatarInfosByShowcase(string uid, IEnumerable<EnkaAvatarInfo> webInfos, CancellationToken token)
{
token.ThrowIfCancellationRequested();
List<EntityAvatarInfo> dbInfos = avatarInfoDbService.GetAvatarInfoListByUid(uid);
@@ -63,7 +63,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
}
token.ThrowIfCancellationRequested();
return avatarInfoDbService.GetAvatarInfoInfoListByUid(uid);
return avatarInfoDbService.GetAvatarInfoListByUid(uid);
}
}
@@ -73,7 +73,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
/// <param name="userAndUid">用户与角色</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
public async ValueTask<List<EnkaAvatarInfo>> UpdateDbAvatarInfosByGameRecordCharacterAsync(UserAndUid userAndUid, CancellationToken token)
public async ValueTask<List<EntityAvatarInfo>> UpdateDbAvatarInfosByGameRecordCharacterAsync(UserAndUid userAndUid, CancellationToken token)
{
token.ThrowIfCancellationRequested();
string uid = userAndUid.Uid.Value;
@@ -121,7 +121,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
}
}
return avatarInfoDbService.GetAvatarInfoInfoListByUid(uid);
return avatarInfoDbService.GetAvatarInfoListByUid(uid);
}
/// <summary>
@@ -130,7 +130,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
/// <param name="userAndUid">用户与角色</param>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
public async ValueTask<List<EnkaAvatarInfo>> UpdateDbAvatarInfosByCalculateAvatarDetailAsync(UserAndUid userAndUid, CancellationToken token)
public async ValueTask<List<EntityAvatarInfo>> UpdateDbAvatarInfosByCalculateAvatarDetailAsync(UserAndUid userAndUid, CancellationToken token)
{
token.ThrowIfCancellationRequested();
string uid = userAndUid.Uid.Value;
@@ -173,7 +173,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
}
}
return avatarInfoDbService.GetAvatarInfoInfoListByUid(uid);
return avatarInfoDbService.GetAvatarInfoListByUid(uid);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Database;
using EnkaAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
using ModelAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
namespace Snap.Hutao.Service.AvatarInfo;
@@ -15,7 +15,7 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
{
private readonly IServiceProvider serviceProvider;
public List<ModelAvatarInfo> GetAvatarInfoListByUid(string uid)
public List<EntityAvatarInfo> GetAvatarInfoListByUid(string uid)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
@@ -24,19 +24,6 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
}
}
public List<EnkaAvatarInfo> GetAvatarInfoInfoListByUid(string uid)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.AvatarInfos
.AsNoTracking()
.Where(i => i.Uid == uid)
.Select(i => i.Info)
.ToList();
}
}
public void DeleteAvatarInfoRangeByUid(string uid)
{
using (IServiceScope scope = serviceProvider.CreateScope())

View File

@@ -10,6 +10,7 @@ using Snap.Hutao.Web.Enka;
using Snap.Hutao.Web.Enka.Model;
using Snap.Hutao.Web.Hoyolab;
using EnkaAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
namespace Snap.Hutao.Service.AvatarInfo;
@@ -56,28 +57,28 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
return new(RefreshResult.ShowcaseNotOpen, default);
}
List<EnkaAvatarInfo> list = avatarInfoDbBulkOperation.UpdateDbAvatarInfosByShowcase(userAndUid.Uid.Value, resp.AvatarInfoList, token);
List<EntityAvatarInfo> list = avatarInfoDbBulkOperation.UpdateDbAvatarInfosByShowcase(userAndUid.Uid.Value, resp.AvatarInfoList, token);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
}
case RefreshOption.RequestFromHoyolabGameRecord:
{
List<EnkaAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndUid, token).ConfigureAwait(false);
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
}
case RefreshOption.RequestFromHoyolabCalculate:
{
List<EnkaAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndUid, token).ConfigureAwait(false);
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResult.Ok, summary);
}
default:
{
List<EnkaAvatarInfo> list = avatarInfoDbService.GetAvatarInfoInfoListByUid(userAndUid.Uid.Value);
List<EntityAvatarInfo> list = avatarInfoDbService.GetAvatarInfoListByUid(userAndUid.Uid.Value);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
return new(RefreshResult.Ok, summary.Avatars.Count == 0 ? null : summary);
@@ -96,7 +97,7 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
?? await enkaClient.GetDataAsync(uid, token).ConfigureAwait(false);
}
private async ValueTask<Summary> GetSummaryCoreAsync(IEnumerable<EnkaAvatarInfo> avatarInfos, CancellationToken token)
private async ValueTask<Summary> GetSummaryCoreAsync(IEnumerable<Model.Entity.AvatarInfo> avatarInfos, CancellationToken token)
{
using (ValueStopwatch.MeasureExecution(logger))
{

View File

@@ -17,5 +17,5 @@ internal interface ISummaryFactory
/// <param name="avatarInfos">角色列表</param>
/// <param name="token">取消令牌</param>
/// <returns>简述对象</returns>
ValueTask<Summary> CreateAsync(IEnumerable<Web.Enka.Model.AvatarInfo> avatarInfos, CancellationToken token);
ValueTask<Summary> CreateAsync(IEnumerable<Model.Entity.AvatarInfo> avatarInfos, CancellationToken token);
}

View File

@@ -6,6 +6,7 @@ using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.AvatarProperty;
using Snap.Hutao.Web.Enka.Model;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon;
using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
@@ -21,7 +22,12 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
[HighQuality]
internal sealed class SummaryAvatarFactory
{
private static readonly DateTimeOffset DefaultRefreshTime = new(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0));
private readonly ModelAvatarInfo avatarInfo;
private readonly DateTimeOffset showcaseRefreshTime;
private readonly DateTimeOffset gameRecordRefreshTime;
private readonly DateTimeOffset calculatorRefreshTime;
private readonly SummaryMetadataContext metadataContext;
/// <summary>
@@ -29,10 +35,14 @@ internal sealed class SummaryAvatarFactory
/// </summary>
/// <param name="metadataContext">元数据上下文</param>
/// <param name="avatarInfo">角色信息</param>
public SummaryAvatarFactory(SummaryMetadataContext metadataContext, ModelAvatarInfo avatarInfo)
public SummaryAvatarFactory(SummaryMetadataContext metadataContext, EntityAvatarInfo avatarInfo)
{
this.metadataContext = metadataContext;
this.avatarInfo = avatarInfo;
this.avatarInfo = avatarInfo.Info;
showcaseRefreshTime = avatarInfo.ShowcaseRefreshTime;
gameRecordRefreshTime = avatarInfo.GameRecordRefreshTime;
calculatorRefreshTime = avatarInfo.CalculatorRefreshTime;
}
/// <summary>
@@ -67,6 +77,17 @@ internal sealed class SummaryAvatarFactory
Weapon = reliquaryAndWeapon.Weapon,
Reliquaries = reliquaryAndWeapon.Reliquaries,
Score = $"{reliquaryAndWeapon.Reliquaries.Sum(r => r.Score):F2}",
// times
ShowcaseRefreshTimeFormat = showcaseRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
? SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed
: SH.ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat.Format(showcaseRefreshTime),
GameRecordRefreshTimeFormat = gameRecordRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
? SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed
: SH.ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat.Format(gameRecordRefreshTime),
CalculatorRefreshTimeFormat = calculatorRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime
? SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed
: SH.ServiceAvatarInfoSummaryCalculatorRefreshTimeFormat.Format(calculatorRefreshTime),
};
ApplyCostumeIconOrDefault(ref propertyAvatar, avatar);

View File

@@ -4,7 +4,6 @@
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.ViewModel.AvatarProperty;
using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
@@ -19,7 +18,7 @@ internal sealed partial class SummaryFactory : ISummaryFactory
private readonly IMetadataService metadataService;
/// <inheritdoc/>
public async ValueTask<Summary> CreateAsync(IEnumerable<ModelAvatarInfo> avatarInfos, CancellationToken token)
public async ValueTask<Summary> CreateAsync(IEnumerable<Model.Entity.AvatarInfo> avatarInfos, CancellationToken token)
{
SummaryMetadataContext metadataContext = new()
{
@@ -35,7 +34,7 @@ internal sealed partial class SummaryFactory : ISummaryFactory
return new()
{
Avatars = avatarInfos
.Where(a => !AvatarIds.IsPlayer(a.AvatarId))
.Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId))
.Select(a => new SummaryAvatarFactory(metadataContext, a).Create())
.OrderByDescending(a => a.LevelNumber)
.ThenBy(a => a.Name)

View File

@@ -1,8 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using EnkaAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
using ModelAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
namespace Snap.Hutao.Service.AvatarInfo;
@@ -10,7 +9,5 @@ internal interface IAvatarInfoDbService
{
void DeleteAvatarInfoRangeByUid(string uid);
List<EnkaAvatarInfo> GetAvatarInfoInfoListByUid(string uid);
List<ModelAvatarInfo> GetAvatarInfoListByUid(string uid);
List<EntityAvatarInfo> GetAvatarInfoListByUid(string uid);
}

View File

@@ -227,7 +227,7 @@
Grid.Row="1"
DisplayMode="Inline"
IsPaneOpen="True"
OpenPaneLength="{StaticResource CompatSplitViewOpenPaneLength}"
OpenPaneLength="{StaticResource CompatSplitViewOpenPaneLength3}"
PaneBackground="Transparent">
<SplitView.Pane>
<ListView ItemsSource="{Binding Summary.Avatars}" SelectedItem="{Binding SelectedAvatar, Mode=TwoWay}">
@@ -236,7 +236,8 @@
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<shci:CachedImage
Grid.Column="0"
@@ -245,13 +246,34 @@
Margin="0,0,12,12"
Source="{Binding SideIcon, Mode=OneWay}"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Margin="12,0,0,0" Text="{Binding Name}"/>
<TextBlock
Margin="12,0,0,0"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"/>
<TextBlock
Margin="12,0,0,0"
Opacity="0.7"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Level}"/>
</StackPanel>
<StackPanel
Grid.Column="2"
VerticalAlignment="Center"
Opacity="0.7">
<TextBlock
FontSize="11"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding ShowcaseRefreshTimeFormat}"/>
<TextBlock
FontSize="11"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding GameRecordRefreshTimeFormat}"/>
<TextBlock
FontSize="11"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding CalculatorRefreshTimeFormat}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>

View File

@@ -93,6 +93,12 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// </summary>
public uint FetterLevel { get; set; }
public string ShowcaseRefreshTimeFormat { get; set; }
public string GameRecordRefreshTimeFormat { get; set; }
public string CalculatorRefreshTimeFormat { get; set; }
/// <summary>
/// Id
/// </summary>