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();
}
/// <inheritdoc/>
public override string ToString()
{
return Value.ToString();
}
}
""");

View File

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

View File

@@ -9,7 +9,7 @@ namespace Snap.Hutao.Control;
[TemplateVisualState(Name = "LoadingOut", GroupName = "CommonStates")]
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;

View File

@@ -11,20 +11,6 @@ namespace Snap.Hutao.Extension;
/// </summary>
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)]
public static bool IsNullOrEmpty<TSource>([NotNullWhen(false)][MaybeNullWhen(true)] this Collection<TSource>? source)
{
@@ -57,4 +43,17 @@ internal static partial class EnumerableExtension
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,
};
}
/// <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>
internal static string ModelMetadataTowerGoalTypeDefeatMonsters {
get {
@@ -871,7 +871,7 @@ namespace Snap.Hutao.Resource.Localization {
}
/// <summary>
/// 查找类似 守 的本地化字符串。
/// 查找类似 守护目标 的本地化字符串。
/// </summary>
internal static string ModelMetadataTowerGoalTypeDefendTarget {
get {
@@ -880,7 +880,7 @@ namespace Snap.Hutao.Resource.Localization {
}
/// <summary>
/// 查找类似 附加 的本地化字符串。
/// 查找类似 附加:增援怪物 的本地化字符串。
/// </summary>
internal static string ModelMetadataTowerWaveTypeAdditional {
get {
@@ -1060,7 +1060,7 @@ namespace Snap.Hutao.Resource.Localization {
}
/// <summary>
/// 查找类似 第一波附加:第一波补充怪物 的本地化字符串。
/// 查找类似 第一波附加:增援第一波怪物 的本地化字符串。
/// </summary>
internal static string ModelMetadataTowerWaveTypeWave1Additional {
get {

View File

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

View File

@@ -4,7 +4,9 @@
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
@@ -70,10 +72,18 @@ internal interface IMetadataServiceIdDataMap
/// <returns>字典</returns>
ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default);
ValueTask<Dictionary<TowerScheduleId, TowerSchedule>> GetIdToTowerScheduleMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取ID到武器的字典
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>Id到武器的字典</returns>
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.Avatar;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
@@ -41,6 +43,21 @@ internal sealed partial class MetadataService
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/>
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);
}
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/>
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);
}
public ValueTask<Dictionary<MonsterRelationshipId, Monster>> GetRelationshipIdToMonsterMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<MonsterRelationshipId, Monster>(FileNameMonster, m => m.RelationshipId, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default)
{

View File

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

View File

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

View File

@@ -15,19 +15,16 @@ internal sealed partial class SpiralAbyssRecordDbService : ISpiralAbyssRecordDbS
{
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())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
List<SpiralAbyssEntry> entries = await appDbContext.SpiralAbysses
return await appDbContext.SpiralAbysses
.Where(s => s.Uid == uid)
.OrderByDescending(s => s.ScheduleId)
.ToListAsync()
.ConfigureAwait(false);
return entries.ToObservableCollection();
}
}

View File

@@ -3,6 +3,10 @@
using Snap.Hutao.Core.DependencyInjection.Abstraction;
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.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Response;
@@ -18,15 +22,35 @@ namespace Snap.Hutao.Service.SpiralAbyss;
[Injection(InjectAs.Scoped, typeof(ISpiralAbyssRecordService))]
internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordService
{
private readonly ITaskContext taskContext;
private readonly IOverseaSupportFactory<IGameRecordClient> gameRecordClientFactory;
private readonly ISpiralAbyssRecordDbService spiralAbyssRecordDbService;
private readonly IMetadataService metadataService;
private readonly ITaskContext taskContext;
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/>
public async ValueTask<ObservableCollection<SpiralAbyssEntry>> GetSpiralAbyssCollectionAsync(UserAndUid userAndUid)
public async ValueTask<ObservableCollection<SpiralAbyssView>> GetSpiralAbyssViewCollectionAsync(UserAndUid userAndUid)
{
if (uid != userAndUid.Uid.Value)
{
@@ -34,9 +58,15 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
}
uid = userAndUid.Uid.Value;
spiralAbysses ??= await spiralAbyssRecordDbService
.GetSpiralAbyssEntryCollectionByUidAsync(userAndUid.Uid.Value)
.ConfigureAwait(false);
if (spiralAbysses is null)
{
List<SpiralAbyssEntry> list = await spiralAbyssRecordDbService
.GetSpiralAbyssEntryListByUidAsync(userAndUid.Uid.Value)
.ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(metadataContext);
spiralAbysses = list.SelectList(entity => SpiralAbyssView.From(entity, metadataContext)).ToObservableCollection();
}
return spiralAbysses;
}
@@ -44,6 +74,11 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
/// <inheritdoc/>
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.Current).ConfigureAwait(false);
}
@@ -60,20 +95,26 @@ internal sealed partial class SpiralAbyssRecordService : ISpiralAbyssRecordServi
Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss webSpiralAbyss = response.Data;
ArgumentNullException.ThrowIfNull(spiralAbysses);
if (spiralAbysses.SingleOrDefault(s => s.ScheduleId == webSpiralAbyss.ScheduleId) is { } existEntry)
{
await taskContext.SwitchToMainThreadAsync();
existEntry.UpdateSpiralAbyss(webSpiralAbyss);
ArgumentNullException.ThrowIfNull(metadataContext);
int index = spiralAbysses.FirstIndexOf(s => s.Entity.ScheduleId == webSpiralAbyss.ScheduleId);
if (index > 0)
{
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
{
SpiralAbyssEntry newEntry = SpiralAbyssEntry.From(userAndUid.Uid.Value, webSpiralAbyss);
await taskContext.SwitchToMainThreadAsync();
spiralAbysses.Insert(0, newEntry);
spiralAbysses.Insert(0, SpiralAbyssView.From(newEntry, metadataContext));
await taskContext.SwitchToBackgroundAsync();
await spiralAbyssRecordDbService.AddSpiralAbyssEntryAsync(newEntry).ConfigureAwait(false);

View File

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

View File

@@ -27,15 +27,15 @@
IsPaneOpen="True"
OpenPaneLength="120"
PaneBackground="Transparent"
Visibility="{Binding SpiralAbyssView, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
Visibility="{Binding SelectedView, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<SplitView.Pane>
<ListView
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
ItemsSource="{Binding SpiralAbyssEntries}"
SelectedItem="{Binding SelectedEntry, Mode=TwoWay}">
SelectedItem="{Binding SelectedView, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Schedule}"/>
<TextBlock Text="{Binding Entity.Schedule}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
@@ -55,30 +55,32 @@
Label="{shcm:ResourceString Name=ViewSpiralAbyssRefresh}"/>
</CommandBar>
</Pivot.RightHeader>
<PivotItem DataContext="{Binding SpiralAbyssView}" Header="{shcm:ResourceString Name=ViewSpiralAbyssStatistics}">
<PivotItem DataContext="{Binding SelectedView}" Header="{shcm:ResourceString Name=ViewSpiralAbyssStatistics}">
<ScrollViewer>
<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>
<ColumnDefinition MaxWidth="600"/>
<ColumnDefinition Width="286"/>
<ColumnDefinition Width="286"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
Margin="16,0,8,16"
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 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.ItemsPanel>
<ItemsPanelTemplate>
@@ -88,13 +90,19 @@
<ItemsControl.ItemTemplate>
<DataTemplate>
<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>
</DataTemplate>
</ItemsControl.ItemTemplate>
</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}">
<StackPanel Orientation="Horizontal">
<TextBlock
@@ -166,10 +174,9 @@
</cwc:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</PivotItem>
<PivotItem DataContext="{Binding SpiralAbyssView}" Header="{shcm:ResourceString Name=ViewSpiralAbyssDetail}">
<PivotItem DataContext="{Binding SelectedView}" Header="{shcm:ResourceString Name=ViewSpiralAbyssDetail}">
<ScrollViewer VerticalAlignment="Top" HorizontalScrollBarVisibility="Auto">
<ItemsControl
Margin="16,16,0,0"
@@ -289,7 +296,7 @@
</Grid>
</SplitView.Content>
</SplitView>
<Grid Visibility="{Binding SpiralAbyssView, Converter={StaticResource EmptyObjectToVisibilityRevertConverter}}">
<Grid Visibility="{Binding SelectedView, Converter={StaticResource EmptyObjectToVisibilityRevertConverter}}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<shci:CachedImage
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 HomaSpiralAbyssClient spiralAbyssClient;
private readonly IMetadataService metadataService;
private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext;
private readonly IUserService userService;
private Dictionary<AvatarId, Model.Metadata.Avatar.Avatar>? idAvatarMap;
private ObservableCollection<SpiralAbyssEntry>? spiralAbyssEntries;
private SpiralAbyssEntry? selectedEntry;
private SpiralAbyssView? spiralAbyssView;
private ObservableCollection<SpiralAbyssView>? spiralAbyssEntries;
private SpiralAbyssView? selectedView;
/// <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>
public SpiralAbyssEntry? SelectedEntry
{
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); }
public SpiralAbyssView? SelectedView { get => selectedView; set => SetProperty(ref selectedView, value); }
/// <inheritdoc/>
public void Receive(UserChangedMessage message)
@@ -73,17 +53,14 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
}
else
{
SpiralAbyssView = null;
SelectedView = null;
}
}
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))
{
await UpdateSpiralAbyssCollectionAsync(userAndUid).ConfigureAwait(false);
@@ -100,13 +77,13 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
private async ValueTask UpdateSpiralAbyssCollectionAsync(UserAndUid userAndUid)
{
ObservableCollection<SpiralAbyssEntry>? temp = null;
ObservableCollection<SpiralAbyssView>? collection = null;
try
{
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
temp = await spiralAbyssRecordService
.GetSpiralAbyssCollectionAsync(userAndUid)
collection = await spiralAbyssRecordService
.GetSpiralAbyssViewCollectionAsync(userAndUid)
.ConfigureAwait(false);
}
}
@@ -115,8 +92,8 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
}
await taskContext.SwitchToMainThreadAsync();
SpiralAbyssEntries = temp;
SelectedEntry = SpiralAbyssEntries?.FirstOrDefault();
SpiralAbyssEntries = collection;
SelectedView = SpiralAbyssEntries?.FirstOrDefault();
}
[Command("RefreshCommand")]
@@ -140,7 +117,7 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
}
await taskContext.SwitchToMainThreadAsync();
SelectedEntry = SpiralAbyssEntries.FirstOrDefault();
SelectedView = SpiralAbyssEntries.FirstOrDefault();
}
}
}

View File

@@ -1,6 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// 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;
namespace Snap.Hutao.ViewModel.SpiralAbyss;
@@ -9,27 +14,47 @@ namespace Snap.Hutao.ViewModel.SpiralAbyss;
/// 深渊视图
/// </summary>
[HighQuality]
internal sealed class SpiralAbyssView
internal sealed class SpiralAbyssView : IEntityOnly<SpiralAbyssEntry>,
IMappingFrom<SpiralAbyssView, SpiralAbyssEntry, SpiralAbyssMetadataContext>
{
private readonly SpiralAbyssEntry entity;
/// <summary>
/// 构造一个新的深渊视图
/// </summary>
/// <param name="spiralAbyss">深渊信息</param>
/// <param name="entity">实体</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;
TotalStar = spiralAbyss.TotalStar;
MaxFloor = spiralAbyss.MaxFloor;
Reveals = spiralAbyss.RevealRank.SelectList(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId]));
Defeat = spiralAbyss.DefeatRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
Damage = spiralAbyss.DamageRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
TakeDamage = spiralAbyss.TakeDamageRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
NormalSkill = spiralAbyss.NormalSkillRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
EnergySkill = spiralAbyss.EnergySkillRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
Floors = spiralAbyss.Floors.Select(f => new FloorView(f, idAvatarMap)).Reverse().ToList();
Reveals = spiralAbyss.RevealRank.SelectList(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId]));
Defeat = spiralAbyss.DefeatRank.Select(r => new RankAvatar(r.Value, context.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, context.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, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
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>
@@ -79,4 +104,9 @@ internal sealed class SpiralAbyssView
/// 层信息
/// </summary>
public List<FloorView> Floors { get; }
public static SpiralAbyssView From(SpiralAbyssEntry entity, SpiralAbyssMetadataContext context)
{
return new(entity, context);
}
}