spiral abyss rework

This commit is contained in:
DismissedLight
2023-09-17 16:05:10 +08:00
parent 98ca533706
commit 3db52e8184
18 changed files with 239 additions and 120 deletions

View File

@@ -89,6 +89,12 @@ internal sealed class IdentityGenerator : IIncrementalGenerator
{ {
return Value.GetHashCode(); return Value.GetHashCode();
} }
/// <inheritdoc/>
public override string ToString()
{
return Value.ToString();
}
} }
"""); """);

View File

@@ -2,8 +2,8 @@
x:Class="Snap.Hutao.App" x:Class="Snap.Hutao.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwcw="using:CommunityToolkit.WinUI.Controls"
xmlns:cwc="using:CommunityToolkit.WinUI.Converters" xmlns:cwc="using:CommunityToolkit.WinUI.Converters"
xmlns:cwcw="using:CommunityToolkit.WinUI.Controls"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:shci="using:Snap.Hutao.Control.Image" xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter" xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"

View File

@@ -9,7 +9,7 @@ namespace Snap.Hutao.Control;
[TemplateVisualState(Name = "LoadingOut", GroupName = "CommonStates")] [TemplateVisualState(Name = "LoadingOut", GroupName = "CommonStates")]
internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
{ {
private static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged)); public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged));
private FrameworkElement? presenter; private FrameworkElement? presenter;

View File

@@ -11,20 +11,6 @@ namespace Snap.Hutao.Extension;
/// </summary> /// </summary>
internal static partial class EnumerableExtension internal static partial class EnumerableExtension
{ {
/// <summary>
/// 尝试添加物品
/// </summary>
/// <typeparam name="T">物品类型</typeparam>
/// <param name="collection">集合</param>
/// <param name="item">物品</param>
public static void AddIfNotContains<T>(this Collection<T> collection, T item)
{
if (!collection.Contains(item))
{
collection.Add(item);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNullOrEmpty<TSource>([NotNullWhen(false)][MaybeNullWhen(true)] this Collection<TSource>? source) public static bool IsNullOrEmpty<TSource>([NotNullWhen(false)][MaybeNullWhen(true)] this Collection<TSource>? source)
{ {
@@ -57,4 +43,17 @@ internal static partial class EnumerableExtension
return count; return count;
} }
public static int FirstIndexOf<T>(this Collection<T> collection, Func<T, bool> predicate)
{
for (int index = 0; index < collection.Count; index++)
{
if (predicate(collection[index]))
{
return index;
}
}
return -1;
}
} }

View File

@@ -59,14 +59,4 @@ internal sealed class SpiralAbyssEntry : ObservableObject,
SpiralAbyss = spiralAbyss, SpiralAbyss = spiralAbyss,
}; };
} }
/// <summary>
/// 更新深渊信息
/// </summary>
/// <param name="spiralAbyss">深渊信息</param>
public void UpdateSpiralAbyss(Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss spiralAbyss)
{
SpiralAbyss = spiralAbyss;
OnPropertyChanged(nameof(SpiralAbyss));
}
} }

View File

@@ -862,7 +862,7 @@ namespace Snap.Hutao.Resource.Localization {
} }
/// <summary> /// <summary>
/// 查找类似 打怪 的本地化字符串。 /// 查找类似 击败怪物 的本地化字符串。
/// </summary> /// </summary>
internal static string ModelMetadataTowerGoalTypeDefeatMonsters { internal static string ModelMetadataTowerGoalTypeDefeatMonsters {
get { get {
@@ -871,7 +871,7 @@ namespace Snap.Hutao.Resource.Localization {
} }
/// <summary> /// <summary>
/// 查找类似 守 的本地化字符串。 /// 查找类似 守护目标 的本地化字符串。
/// </summary> /// </summary>
internal static string ModelMetadataTowerGoalTypeDefendTarget { internal static string ModelMetadataTowerGoalTypeDefendTarget {
get { get {
@@ -880,7 +880,7 @@ namespace Snap.Hutao.Resource.Localization {
} }
/// <summary> /// <summary>
/// 查找类似 附加 的本地化字符串。 /// 查找类似 附加:增援怪物 的本地化字符串。
/// </summary> /// </summary>
internal static string ModelMetadataTowerWaveTypeAdditional { internal static string ModelMetadataTowerWaveTypeAdditional {
get { get {
@@ -1060,7 +1060,7 @@ namespace Snap.Hutao.Resource.Localization {
} }
/// <summary> /// <summary>
/// 查找类似 第一波附加:第一波补充怪物 的本地化字符串。 /// 查找类似 第一波附加:增援第一波怪物 的本地化字符串。
/// </summary> /// </summary>
internal static string ModelMetadataTowerWaveTypeWave1Additional { internal static string ModelMetadataTowerWaveTypeWave1Additional {
get { get {

View File

@@ -414,13 +414,13 @@
<comment>Need EXACT same string in game</comment> <comment>Need EXACT same string in game</comment>
</data> </data>
<data name="ModelMetadataTowerGoalTypeDefeatMonsters" xml:space="preserve"> <data name="ModelMetadataTowerGoalTypeDefeatMonsters" xml:space="preserve">
<value>打怪</value> <value>击败怪物</value>
</data> </data>
<data name="ModelMetadataTowerGoalTypeDefendTarget" xml:space="preserve"> <data name="ModelMetadataTowerGoalTypeDefendTarget" xml:space="preserve">
<value>守</value> <value>守护目标</value>
</data> </data>
<data name="ModelMetadataTowerWaveTypeAdditional" xml:space="preserve"> <data name="ModelMetadataTowerWaveTypeAdditional" xml:space="preserve">
<value>附加</value> <value>附加:增援怪物</value>
</data> </data>
<data name="ModelMetadataTowerWaveTypeGroupA" xml:space="preserve"> <data name="ModelMetadataTowerWaveTypeGroupA" xml:space="preserve">
<value>A组不同的组同时在场各自分波独立</value> <value>A组不同的组同时在场各自分波独立</value>
@@ -480,7 +480,7 @@
<value>第一波:击败所有怪物,下一波才会出现</value> <value>第一波:击败所有怪物,下一波才会出现</value>
</data> </data>
<data name="ModelMetadataTowerWaveTypeWave1Additional" xml:space="preserve"> <data name="ModelMetadataTowerWaveTypeWave1Additional" xml:space="preserve">
<value>第一波附加:第一波补充怪物</value> <value>第一波附加:增援第一波怪物</value>
</data> </data>
<data name="ModelMetadataTowerWaveTypeWave2" xml:space="preserve"> <data name="ModelMetadataTowerWaveTypeWave2" xml:space="preserve">
<value>第二波:击败所有怪物,下一波才会出现</value> <value>第二波:击败所有怪物,下一波才会出现</value>

View File

@@ -4,7 +4,9 @@
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item; using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary; using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Metadata.Weapon; using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
@@ -70,10 +72,18 @@ internal interface IMetadataServiceIdDataMap
/// <returns>字典</returns> /// <returns>字典</returns>
ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default); ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default);
ValueTask<Dictionary<TowerScheduleId, TowerSchedule>> GetIdToTowerScheduleMapAsync(CancellationToken token = default);
/// <summary> /// <summary>
/// 异步获取ID到武器的字典 /// 异步获取ID到武器的字典
/// </summary> /// </summary>
/// <param name="token">取消令牌</param> /// <param name="token">取消令牌</param>
/// <returns>Id到武器的字典</returns> /// <returns>Id到武器的字典</returns>
ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default); ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default);
ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(CancellationToken token = default);
ValueTask<Dictionary<TowerFloorId, TowerFloor>> GetIdToTowerFloorMapAsync(CancellationToken token = default);
ValueTask<Dictionary<MonsterRelationshipId, Monster>> GetRelationshipIdToMonsterMapAsync(CancellationToken token = default);
} }

View File

@@ -6,7 +6,9 @@ using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item; using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary; using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Metadata.Weapon; using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
@@ -41,6 +43,21 @@ internal sealed partial class MetadataService
return memoryCache.Set(cacheKey, dict); return memoryCache.Set(cacheKey, dict);
} }
public async ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(CancellationToken token = default)
{
string cacheKey = $"{nameof(MetadataService)}.Cache.{FileNameTowerLevel}.Map.Group.{nameof(TowerLevelGroupId)}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<TowerLevelGroupId, List<TowerLevel>>)value;
}
List<TowerLevel> list = await FromCacheOrFileAsync<List<TowerLevel>>(FileNameTowerLevel, token).ConfigureAwait(false);
Dictionary<TowerLevelGroupId, List<TowerLevel>> dict = list.GroupBy(l => l.GroupId).ToDictionary(g => g.Key, g => g.ToList());
return memoryCache.Set(cacheKey, dict);
}
/// <inheritdoc/> /// <inheritdoc/>
public ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(CancellationToken token = default) public ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(CancellationToken token = default)
{ {
@@ -102,6 +119,16 @@ internal sealed partial class MetadataService
return FromCacheAsDictionaryAsync<ReliquaryMainAffixId, FightProperty, ReliquaryMainAffix>(FileNameReliquaryMainAffix, r => r.Id, r => r.Type, token); return FromCacheAsDictionaryAsync<ReliquaryMainAffixId, FightProperty, ReliquaryMainAffix>(FileNameReliquaryMainAffix, r => r.Id, r => r.Type, token);
} }
public ValueTask<Dictionary<TowerFloorId, TowerFloor>> GetIdToTowerFloorMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<TowerFloorId, TowerFloor>(FileNameTowerFloor, t => t.Id, token);
}
public ValueTask<Dictionary<TowerScheduleId, TowerSchedule>> GetIdToTowerScheduleMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<TowerScheduleId, TowerSchedule>(FileNameTowerSchedule, t => t.Id, token);
}
/// <inheritdoc/> /// <inheritdoc/>
public ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default) public ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default)
{ {
@@ -126,6 +153,11 @@ internal sealed partial class MetadataService
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameWeaponCurve, w => w.Level, w => w.Map, token); return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameWeaponCurve, w => w.Level, w => w.Map, token);
} }
public ValueTask<Dictionary<MonsterRelationshipId, Monster>> GetRelationshipIdToMonsterMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<MonsterRelationshipId, Monster>(FileNameMonster, m => m.RelationshipId, token);
}
/// <inheritdoc/> /// <inheritdoc/>
public ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default) public ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default)
{ {

View File

@@ -10,7 +10,7 @@ internal interface ISpiralAbyssRecordDbService
{ {
ValueTask AddSpiralAbyssEntryAsync(SpiralAbyssEntry entry); ValueTask AddSpiralAbyssEntryAsync(SpiralAbyssEntry entry);
ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssEntryCollectionByUidAsync(string uid); ValueTask<List<SpiralAbyssEntry>> GetSpiralAbyssEntryListByUidAsync(string uid);
ValueTask UpdateSpiralAbyssEntryAsync(SpiralAbyssEntry entry); ValueTask UpdateSpiralAbyssEntryAsync(SpiralAbyssEntry entry);
} }

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity;
using Snap.Hutao.ViewModel.SpiralAbyss;
using Snap.Hutao.ViewModel.User; using Snap.Hutao.ViewModel.User;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -18,7 +19,9 @@ internal interface ISpiralAbyssRecordService
/// </summary> /// </summary>
/// <param name="userAndUid">当前角色</param> /// <param name="userAndUid">当前角色</param>
/// <returns>深渊记录集合</returns> /// <returns>深渊记录集合</returns>
ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid); ValueTask<ObservableCollection<SpiralAbyssView>> GetSpiralAbyssViewCollectionAsync(UserAndUid userAndUid);
ValueTask<bool> InitializeAsync();
/// <summary> /// <summary>
/// 异步刷新深渊记录 /// 异步刷新深渊记录

View File

@@ -15,19 +15,16 @@ internal sealed partial class SpiralAbyssRecordDbService : ISpiralAbyssRecordDbS
{ {
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
public async ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssEntryCollectionByUidAsync(string uid) public async ValueTask<List<SpiralAbyssEntry>> GetSpiralAbyssEntryListByUidAsync(string uid)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.SpiralAbysses
List<SpiralAbyssEntry> entries = await appDbContext.SpiralAbysses
.Where(s => s.Uid == uid) .Where(s => s.Uid == uid)
.OrderByDescending(s => s.ScheduleId) .OrderByDescending(s => s.ScheduleId)
.ToListAsync() .ToListAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
return entries.ToObservableCollection();
} }
} }

View File

@@ -3,6 +3,10 @@
using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.ViewModel.SpiralAbyss;
using Snap.Hutao.ViewModel.User; using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Response; using Snap.Hutao.Web.Response;
@@ -18,15 +22,35 @@ namespace Snap.Hutao.Service.SpiralAbyss;
[Injection(InjectAs.Scoped, typeof(ISpiralAbyssRecordService))] [Injection(InjectAs.Scoped, typeof(ISpiralAbyssRecordService))]
internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordService internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordService
{ {
private readonly ITaskContext taskContext;
private readonly IOverseaSupportFactory<IGameRecordClient> gameRecordClientFactory; private readonly IOverseaSupportFactory<IGameRecordClient> gameRecordClientFactory;
private readonly ISpiralAbyssRecordDbService spiralAbyssRecordDbService; private readonly ISpiralAbyssRecordDbService spiralAbyssRecordDbService;
private readonly IMetadataService metadataService;
private readonly ITaskContext taskContext;
private string? uid; private string? uid;
private ObservableCollection<SpiralAbyssEntry>? spiralAbysses; private ObservableCollection<SpiralAbyssView>? spiralAbysses;
private SpiralAbyssMetadataContext? metadataContext;
public async ValueTask<bool> InitializeAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
metadataContext = new()
{
IdScheduleMap = await metadataService.GetIdToTowerScheduleMapAsync().ConfigureAwait(false),
IdFloorMap = await metadataService.GetIdToTowerFloorMapAsync().ConfigureAwait(false),
IdLevelGroupMap = await metadataService.GetGroupIdToTowerLevelGroupMapAsync().ConfigureAwait(false),
IdMonsterMap = await metadataService.GetRelationshipIdToMonsterMapAsync().ConfigureAwait(false),
IdAvatarMap = AvatarIds.WithPlayers(await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false)),
};
return true;
}
return false;
}
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid) public async ValueTask<ObservableCollection<SpiralAbyssView>> GetSpiralAbyssViewCollectionAsync(UserAndUid userAndUid)
{ {
if (uid != userAndUid.Uid.Value) if (uid != userAndUid.Uid.Value)
{ {
@@ -34,16 +58,27 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
} }
uid = userAndUid.Uid.Value; uid = userAndUid.Uid.Value;
spiralAbysses ??= await spiralAbyssRecordDbService if (spiralAbysses is null)
.GetSpiralAbyssEntryCollectionByUidAsync(userAndUid.Uid.Value) {
List<SpiralAbyssEntry> list = await spiralAbyssRecordDbService
.GetSpiralAbyssEntryListByUidAsync(userAndUid.Uid.Value)
.ConfigureAwait(false); .ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(metadataContext);
spiralAbysses = list.SelectList(entity => SpiralAbyssView.From(entity, metadataContext)).ToObservableCollection();
}
return spiralAbysses; return spiralAbysses;
} }
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask RefreshSpiralAbyssAsync(UserAndUid userAndUid) public async ValueTask RefreshSpiralAbyssAsync(UserAndUid userAndUid)
{ {
// request the index first
await gameRecordClientFactory
.Create(userAndUid.User.IsOversea)
.GetPlayerInfoAsync(userAndUid)
.ConfigureAwait(false);
await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Last).ConfigureAwait(false); await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Last).ConfigureAwait(false);
await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Current).ConfigureAwait(false); await RefreshSpiralAbyssCoreAsync(userAndUid, SpiralAbyssSchedule.Current).ConfigureAwait(false);
} }
@@ -60,20 +95,26 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data; Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data;
ArgumentNullException.ThrowIfNull(spiralAbysses); ArgumentNullException.ThrowIfNull(spiralAbysses);
if (spiralAbysses.SingleOrDefault(s => s.ScheduleId == webSpiralAbyss.ScheduleId) is { } existEntry) ArgumentNullException.ThrowIfNull(metadataContext);
{
await taskContext.SwitchToMainThreadAsync();
existEntry.UpdateSpiralAbyss(webSpiralAbyss);
int index = spiralAbysses.FirstIndexOf(s => s.Entity.ScheduleId == webSpiralAbyss.ScheduleId);
if (index > 0)
{
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await spiralAbyssRecordDbService.UpdateSpiralAbyssEntryAsync(existEntry).ConfigureAwait(false); SpiralAbyssView view = spiralAbysses[index];
view.Entity.SpiralAbyss = webSpiralAbyss;
await spiralAbyssRecordDbService.UpdateSpiralAbyssEntryAsync(view.Entity).ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
spiralAbysses.RemoveAt(index);
spiralAbysses.Insert(index, SpiralAbyssView.From(view.Entity, metadataContext));
} }
else else
{ {
SpiralAbyssEntry newEntry = SpiralAbyssEntry.From(userAndUid.Uid.Value, webSpiralAbyss); SpiralAbyssEntry newEntry = SpiralAbyssEntry.From(userAndUid.Uid.Value, webSpiralAbyss);
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
spiralAbysses.Insert(0, newEntry); spiralAbysses.Insert(0, SpiralAbyssView.From(newEntry, metadataContext));
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await spiralAbyssRecordDbService.AddSpiralAbyssEntryAsync(newEntry).ConfigureAwait(false); await spiralAbyssRecordDbService.AddSpiralAbyssEntryAsync(newEntry).ConfigureAwait(false);

View File

@@ -188,12 +188,17 @@
</Style> </Style>
</ListView.ItemContainerStyle> </ListView.ItemContainerStyle>
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate x:DataType="shva:AchievementView">
<Border <Border
Margin="0,4,0,0" Margin="0,4,0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource BorderCardStyle}"> Style="{StaticResource BorderCardStyle}">
<Border.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem IsEnabled="False" Text="{Binding Inner.Id}"/>
</MenuFlyout>
</Border.ContextFlyout>
<Grid MinHeight="48" Padding="8"> <Grid MinHeight="48" Padding="8">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>

View File

@@ -27,15 +27,15 @@
IsPaneOpen="True" IsPaneOpen="True"
OpenPaneLength="120" OpenPaneLength="120"
PaneBackground="Transparent" PaneBackground="Transparent"
Visibility="{Binding SpiralAbyssView, Converter={StaticResource EmptyObjectToVisibilityConverter}}"> Visibility="{Binding SelectedView, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<SplitView.Pane> <SplitView.Pane>
<ListView <ListView
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
ItemsSource="{Binding SpiralAbyssEntries}" ItemsSource="{Binding SpiralAbyssEntries}"
SelectedItem="{Binding SelectedEntry, Mode=TwoWay}"> SelectedItem="{Binding SelectedView, Mode=TwoWay}">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding Schedule}"/> <TextBlock Text="{Binding Entity.Schedule}"/>
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
@@ -55,30 +55,32 @@
Label="{shcm:ResourceString Name=ViewSpiralAbyssRefresh}"/> Label="{shcm:ResourceString Name=ViewSpiralAbyssRefresh}"/>
</CommandBar> </CommandBar>
</Pivot.RightHeader> </Pivot.RightHeader>
<PivotItem DataContext="{Binding SpiralAbyssView}" Header="{shcm:ResourceString Name=ViewSpiralAbyssStatistics}"> <PivotItem DataContext="{Binding SelectedView}" Header="{shcm:ResourceString Name=ViewSpiralAbyssStatistics}">
<ScrollViewer> <ScrollViewer>
<Grid> <Grid>
<Grid.Resources>
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
</Grid.Resources>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="600"/> <ColumnDefinition Width="286"/>
<ColumnDefinition Width="286"/>
<ColumnDefinition Width="auto"/> <ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel <StackPanel
Grid.Column="0" Grid.Column="0"
Margin="16,0,8,16" Margin="16,0,8,16"
Spacing="{StaticResource SettingsCardSpacing}"> Spacing="{StaticResource SettingsCardSpacing}">
<StackPanel.Resources>
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
</StackPanel.Resources>
<cwc:SettingsCard
Margin="0,16,0,0"
Content="{Binding TotalBattleTimes}"
Header="{shcm:ResourceString Name=ViewSpiralAbyssBattleTimes}"/>
<cwc:SettingsCard Content="{Binding TotalStar}" Header="{shcm:ResourceString Name=ViewSpiralAbyssTotalStar}"/>
<cwc:SettingsCard Content="{Binding MaxFloor}" Header="{shcm:ResourceString Name=ViewSpiralAbyssMaxFloor}"/> <cwc:SettingsCard Content="{Binding MaxFloor}" Header="{shcm:ResourceString Name=ViewSpiralAbyssMaxFloor}"/>
<cwc:SettingsCard Content="{Binding TotalBattleTimes}" Header="{shcm:ResourceString Name=ViewSpiralAbyssBattleTimes}"/>
<cwc:SettingsCard Content="{Binding TotalStar}" Header="{shcm:ResourceString Name=ViewSpiralAbyssTotalStar}"/>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewSpiralAbyssReveal}"/>
<TextBlock
Margin="1,6,0,5"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewSpiralAbyssReveal}"/>
<ItemsControl HorizontalAlignment="Left" ItemsSource="{Binding Reveals}"> <ItemsControl HorizontalAlignment="Left" ItemsSource="{Binding Reveals}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
@@ -88,13 +90,19 @@
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<shvc:BottomTextControl Text="{Binding Value}"> <shvc:BottomTextControl Text="{Binding Value}">
<shvc:ItemIcon Icon="{Binding Icon}" Quality="{Binding Quality}"/> <shvc:ItemIcon
Width="52"
Height="52"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
</shvc:BottomTextControl> </shvc:BottomTextControl>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewSpiralAbyssBattleHeader}"/>
</StackPanel>
<StackPanel Grid.Column="1" Spacing="{StaticResource SettingsCardSpacing}">
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewSpiralAbyssDefeat}"> <cwc:SettingsCard Header="{shcm:ResourceString Name=ViewSpiralAbyssDefeat}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock <TextBlock
@@ -166,10 +174,9 @@
</cwc:SettingsCard> </cwc:SettingsCard>
</StackPanel> </StackPanel>
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</PivotItem> </PivotItem>
<PivotItem DataContext="{Binding SpiralAbyssView}" Header="{shcm:ResourceString Name=ViewSpiralAbyssDetail}"> <PivotItem DataContext="{Binding SelectedView}" Header="{shcm:ResourceString Name=ViewSpiralAbyssDetail}">
<ScrollViewer VerticalAlignment="Top" HorizontalScrollBarVisibility="Auto"> <ScrollViewer VerticalAlignment="Top" HorizontalScrollBarVisibility="Auto">
<ItemsControl <ItemsControl
Margin="16,16,0,0" Margin="16,16,0,0"
@@ -289,7 +296,7 @@
</Grid> </Grid>
</SplitView.Content> </SplitView.Content>
</SplitView> </SplitView>
<Grid Visibility="{Binding SpiralAbyssView, Converter={StaticResource EmptyObjectToVisibilityRevertConverter}}"> <Grid Visibility="{Binding SelectedView, Converter={StaticResource EmptyObjectToVisibilityRevertConverter}}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<shci:CachedImage <shci:CachedImage
Width="120" Width="120"

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.ViewModel.SpiralAbyss;
internal sealed class SpiralAbyssMetadataContext
{
public Dictionary<TowerScheduleId, TowerSchedule> IdScheduleMap { get; set; } = default!;
public Dictionary<TowerFloorId, TowerFloor> IdFloorMap { get; set; } = default!;
public Dictionary<TowerLevelGroupId, List<TowerLevel>> IdLevelGroupMap { get; set; } = default!;
public Dictionary<MonsterRelationshipId, Monster> IdMonsterMap { get; set; } = default!;
public Dictionary<AvatarId, Avatar> IdAvatarMap { get; set; } = default!;
}

View File

@@ -27,42 +27,22 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
{ {
private readonly ISpiralAbyssRecordService spiralAbyssRecordService; private readonly ISpiralAbyssRecordService spiralAbyssRecordService;
private readonly HomaSpiralAbyssClient spiralAbyssClient; private readonly HomaSpiralAbyssClient spiralAbyssClient;
private readonly IMetadataService metadataService;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IUserService userService; private readonly IUserService userService;
private Dictionary<AvatarId, Model.Metadata.Avatar.Avatar>? idAvatarMap; private ObservableCollection<SpiralAbyssView>? spiralAbyssEntries;
private ObservableCollection<SpiralAbyssEntry>? spiralAbyssEntries; private SpiralAbyssView? selectedView;
private SpiralAbyssEntry? selectedEntry;
private SpiralAbyssView? spiralAbyssView;
/// <summary> /// <summary>
/// 深渊记录 /// 深渊记录
/// </summary> /// </summary>
public ObservableCollection<SpiralAbyssEntry>? SpiralAbyssEntries { get => spiralAbyssEntries; set => SetProperty(ref spiralAbyssEntries, value); } public ObservableCollection<SpiralAbyssView>? SpiralAbyssEntries { get => spiralAbyssEntries; set => SetProperty(ref spiralAbyssEntries, value); }
/// <summary> /// <summary>
/// 选中的深渊信息 /// 选中的深渊信息
/// </summary> /// </summary>
public SpiralAbyssEntry? SelectedEntry public SpiralAbyssView? SelectedView { get => selectedView; set => SetProperty(ref selectedView, value); }
{
get => selectedEntry; set
{
// We dont need to check the result here,
// just refresh the view anyway.
SetProperty(ref selectedEntry, value);
if (value is not null && idAvatarMap is not null)
{
SpiralAbyssView = new(value.SpiralAbyss, idAvatarMap);
}
}
}
/// <summary>
/// 深渊的只读视图
/// </summary>
public SpiralAbyssView? SpiralAbyssView { get => spiralAbyssView; set => SetProperty(ref spiralAbyssView, value); }
/// <inheritdoc/> /// <inheritdoc/>
public void Receive(UserChangedMessage message) public void Receive(UserChangedMessage message)
@@ -73,17 +53,14 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
} }
else else
{ {
SpiralAbyssView = null; SelectedView = null;
} }
} }
protected override async ValueTask<bool> InitializeUIAsync() protected override async ValueTask<bool> InitializeUIAsync()
{ {
if (await metadataService.InitializeAsync().ConfigureAwait(false)) if (await spiralAbyssRecordService.InitializeAsync().ConfigureAwait(false))
{ {
idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
idAvatarMap = AvatarIds.WithPlayers(idAvatarMap);
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
{ {
await UpdateSpiralAbyssCollectionAsync(userAndUid).ConfigureAwait(false); await UpdateSpiralAbyssCollectionAsync(userAndUid).ConfigureAwait(false);
@@ -100,13 +77,13 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
private async ValueTask UpdateSpiralAbyssCollectionAsync(UserAndUid userAndUid) private async ValueTask UpdateSpiralAbyssCollectionAsync(UserAndUid userAndUid)
{ {
ObservableCollection<SpiralAbyssEntry>? temp = null; ObservableCollection<SpiralAbyssView>? collection = null;
try try
{ {
using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{ {
temp = await spiralAbyssRecordService collection = await spiralAbyssRecordService
.GetSpiralAbyssCollectionAsync(userAndUid) .GetSpiralAbyssViewCollectionAsync(userAndUid)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
} }
@@ -115,8 +92,8 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
} }
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
SpiralAbyssEntries = temp; SpiralAbyssEntries = collection;
SelectedEntry = SpiralAbyssEntries?.FirstOrDefault(); SelectedView = SpiralAbyssEntries?.FirstOrDefault();
} }
[Command("RefreshCommand")] [Command("RefreshCommand")]
@@ -140,7 +117,7 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
} }
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
SelectedEntry = SpiralAbyssEntries.FirstOrDefault(); SelectedView = SpiralAbyssEntries.FirstOrDefault();
} }
} }
} }

View File

@@ -1,6 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.ViewModel.SpiralAbyss; namespace Snap.Hutao.ViewModel.SpiralAbyss;
@@ -9,27 +14,47 @@ namespace Snap.Hutao.ViewModel.SpiralAbyss;
/// 深渊视图 /// 深渊视图
/// </summary> /// </summary>
[HighQuality] [HighQuality]
internal sealed class SpiralAbyssView internal sealed class SpiralAbyssView : IEntityOnly<SpiralAbyssEntry>,
IMappingFrom<SpiralAbyssView, SpiralAbyssEntry, SpiralAbyssMetadataContext>
{ {
private readonly SpiralAbyssEntry entity;
/// <summary> /// <summary>
/// 构造一个新的深渊视图 /// 构造一个新的深渊视图
/// </summary> /// </summary>
/// <param name="spiralAbyss">深渊信息</param> /// <param name="entity">实体</param>
/// <param name="idAvatarMap">Id角色映射</param> /// <param name="idAvatarMap">Id角色映射</param>
public SpiralAbyssView(Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss spiralAbyss, Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap) private SpiralAbyssView(SpiralAbyssEntry entity, SpiralAbyssMetadataContext context)
{ {
this.entity = entity;
Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss spiralAbyss = entity.SpiralAbyss;
TowerSchedule towerSchedule = context.IdScheduleMap[(uint)entity.ScheduleId];
TimeFormatted = $"{towerSchedule.Open:yyyy/MM/dd HH:mm:ss} - {towerSchedule.Close:yyyy/MM/dd HH:mm:ss}";
BlessingName = towerSchedule.BuffName;
Blessings = towerSchedule.Descriptions;
TotalBattleTimes = spiralAbyss.TotalBattleTimes; TotalBattleTimes = spiralAbyss.TotalBattleTimes;
TotalStar = spiralAbyss.TotalStar; TotalStar = spiralAbyss.TotalStar;
MaxFloor = spiralAbyss.MaxFloor; MaxFloor = spiralAbyss.MaxFloor;
Reveals = spiralAbyss.RevealRank.SelectList(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])); Reveals = spiralAbyss.RevealRank.SelectList(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId]));
Defeat = spiralAbyss.DefeatRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault(); Defeat = spiralAbyss.DefeatRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
Damage = spiralAbyss.DamageRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault(); Damage = spiralAbyss.DamageRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
TakeDamage = spiralAbyss.TakeDamageRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault(); TakeDamage = spiralAbyss.TakeDamageRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
NormalSkill = spiralAbyss.NormalSkillRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault(); NormalSkill = spiralAbyss.NormalSkillRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
EnergySkill = spiralAbyss.EnergySkillRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault(); EnergySkill = spiralAbyss.EnergySkillRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
Floors = spiralAbyss.Floors.Select(f => new FloorView(f, idAvatarMap)).Reverse().ToList(); Floors = spiralAbyss.Floors.Select(f => new FloorView(f, context.IdAvatarMap)).Reverse().ToList();
} }
public SpiralAbyssEntry Entity { get => entity; }
public string TimeFormatted { get; }
public string BlessingName { get; }
public List<string> Blessings { get; }
/// <summary> /// <summary>
/// 战斗次数 /// 战斗次数
/// </summary> /// </summary>
@@ -79,4 +104,9 @@ internal sealed class SpiralAbyssView
/// 层信息 /// 层信息
/// </summary> /// </summary>
public List<FloorView> Floors { get; } public List<FloorView> Floors { get; }
public static SpiralAbyssView From(SpiralAbyssEntry entity, SpiralAbyssMetadataContext context)
{
return new(entity, context);
}
} }