cultivation improvement

This commit is contained in:
DismissedLight
2022-12-29 15:03:21 +08:00
parent 26e6d2008e
commit 761049ec17
23 changed files with 535 additions and 255 deletions

View File

@@ -23,6 +23,11 @@ internal static class Activation
/// </summary> /// </summary>
public const string LaunchGame = "LaunchGame"; public const string LaunchGame = "LaunchGame";
/// <summary>
/// 从剪贴板导入成就
/// </summary>
public const string ImportUIAFFromClipBoard = "ImportUIAFFromClipBoard";
private static readonly SemaphoreSlim ActivateSemaphore = new(1); private static readonly SemaphoreSlim ActivateSemaphore = new(1);
/// <summary> /// <summary>
@@ -180,7 +185,7 @@ internal static class Activation
{ {
await ThreadHelper.SwitchToMainThreadAsync(); await ThreadHelper.SwitchToMainThreadAsync();
INavigationAwaiter navigationAwaiter = new NavigationExtra("InvokeByUri"); INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipBoard);
await Ioc.Default await Ioc.Default
.GetRequiredService<INavigationService>() .GetRequiredService<INavigationService>()
.NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true) .NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true)

View File

@@ -65,6 +65,11 @@ public class CultivateItem : ObservableObject
/// </summary> /// </summary>
public bool IsToday { get; } public bool IsToday { get; }
/// <summary>
/// 对应背包物品的个数
/// </summary>
public uint InventoryItemCount { get; set; }
private void FlipIsFinished() private void FlipIsFinished()
{ {
IsFinished = !IsFinished; IsFinished = !IsFinished;

View File

@@ -0,0 +1,51 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Service.Cultivation;
namespace Snap.Hutao.Model.Binding.Cultivation;
/// <summary>
/// 仅用于统计总数的养成物品
/// </summary>
public class StatisticsCultivateItem
{
/// <summary>
/// 构造一个新的统计用养成物品
/// </summary>
/// <param name="inner">材料</param>
/// <param name="entity">实体</param>
public StatisticsCultivateItem(Material inner, Entity.CultivateItem entity)
{
Inner = inner;
Count = entity.Count;
}
/// <summary>
/// 元数据
/// </summary>
public Material Inner { get; }
/// <summary>
/// 对应背包物品的个数
/// </summary>
public int Count { get; set; }
/// <summary>
/// 对应背包物品的个数
/// </summary>
public uint TotalCount { get; set; }
/// <summary>
/// 是否完成
/// </summary>
public bool IsFinished { get => Count >= TotalCount; }
/// <summary>
/// 格式化总数
/// </summary>
public string CountFormatted { get => $"{Count}/{TotalCount}"; }
}

View File

@@ -28,8 +28,14 @@ internal class Team : List<ComplexAvatar>
} }
Rate = $"上场 {team.Rate} 次"; Rate = $"上场 {team.Rate} 次";
Name = TeamPopularNameParser.GetName(ids.ToHashSet());
} }
/// <summary>
/// 队伍俗名
/// </summary>
public string Name { get; set; }
/// <summary> /// <summary>
/// 上场次数 /// 上场次数
/// </summary> /// </summary>

View File

@@ -0,0 +1,51 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata;
using System.Collections.Immutable;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 队伍俗名解析器
/// </summary>
internal static class TeamPopularNameParser
{
/// <summary>
/// 已知的队伍名称
/// </summary>
private static readonly ImmutableDictionary<string, ImmutableHashSet<int>> KnownTeamNames = new Dictionary<string, ImmutableHashSet<int>>()
{
["雷国|雷行香班"] = new HashSet<int>() { AvatarIds.Shougun, AvatarIds.Xingqiu, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["雷国|雷夜香班"] = new HashSet<int>() { AvatarIds.Shougun, AvatarIds.Yelan, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["雷国|雷万香班"] = new HashSet<int>() { AvatarIds.Shougun, AvatarIds.Kazuha, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["雷九|雷九万班"] = new HashSet<int>() { AvatarIds.Shougun, AvatarIds.Sara, AvatarIds.Kazuha, AvatarIds.Bennett, }.ToImmutableHashSet(),
["万达国际"] = new HashSet<int>() { AvatarIds.Kazuha, AvatarIds.Tartaglia, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["胡行钟夜"] = new HashSet<int>() { AvatarIds.Hutao, AvatarIds.Xingqiu, AvatarIds.Zhongli, AvatarIds.Yelan, }.ToImmutableHashSet(),
["激晴|刻皇万妲"] = new HashSet<int>() { AvatarIds.Keqing, AvatarIds.Fischl, AvatarIds.Kazuha, AvatarIds.Nahida, }.ToImmutableHashSet(),
["永冻|神鹤万心"] = new HashSet<int>() { AvatarIds.Ayaka, AvatarIds.Shenhe, AvatarIds.Kazuha, AvatarIds.Kokomi, }.ToImmutableHashSet(),
["永冻|神钟万心"] = new HashSet<int>() { AvatarIds.Ayaka, AvatarIds.Zhongli, AvatarIds.Kazuha, AvatarIds.Kokomi, }.ToImmutableHashSet(),
["融甘|融化甘雨"] = new HashSet<int>() { AvatarIds.Ganyu, AvatarIds.Zhongli, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["妮绽放|女主"] = new HashSet<int>() { AvatarIds.Nilou, AvatarIds.Kokomi, AvatarIds.Nahida, AvatarIds.PlayerGirl, }.ToImmutableHashSet(),
["妮绽放|男主"] = new HashSet<int>() { AvatarIds.Nilou, AvatarIds.Kokomi, AvatarIds.Nahida, AvatarIds.PlayerBoy, }.ToImmutableHashSet(),
["一斗岩队"] = new HashSet<int>() { AvatarIds.Itto, AvatarIds.Albedo, AvatarIds.Zhongli, AvatarIds.Gorou, }.ToImmutableHashSet(),
}.ToImmutableDictionary();
/// <summary>
/// 获取队伍名称
/// </summary>
/// <param name="ids">队伍集</param>
/// <returns>队伍名称</returns>
public static string GetName(HashSet<int> ids)
{
foreach (KeyValuePair<string, ImmutableHashSet<int>> entry in KnownTeamNames)
{
if (entry.Value.SetEquals(ids))
{
return entry.Key;
}
}
return "尚未命名";
}
}

View File

@@ -74,24 +74,24 @@ public enum WeaponType
/// <summary> /// <summary>
/// 法器 /// 法器
/// </summary> /// </summary>
[Description("单手剑")] [Description("法器")]
WEAPON_CATALYST = 10, WEAPON_CATALYST = 10,
/// <summary> /// <summary>
/// 双手剑 /// 双手剑
/// </summary> /// </summary>
[Description("手剑")] [Description("手剑")]
WEAPON_CLAYMORE = 11, WEAPON_CLAYMORE = 11,
/// <summary> /// <summary>
/// 弓 /// 弓
/// </summary> /// </summary>
[Description("单手剑")] [Description("")]
WEAPON_BOW = 12, WEAPON_BOW = 12,
/// <summary> /// <summary>
/// 长柄武器 /// 长柄武器
/// </summary> /// </summary>
[Description("单手剑")] [Description("长柄武器")]
WEAPON_POLE = 13, WEAPON_POLE = 13,
} }

View File

@@ -12,7 +12,7 @@
<Identity <Identity
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d" Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
Publisher="CN=DGP Studio" Publisher="CN=DGP Studio"
Version="1.2.16.0" /> Version="1.2.19.0" />
<Properties> <Properties>
<DisplayName>胡桃</DisplayName> <DisplayName>胡桃</DisplayName>

View File

@@ -22,6 +22,7 @@ namespace Snap.Hutao.Service.Achievement;
[Injection(InjectAs.Scoped, typeof(IAchievementService))] [Injection(InjectAs.Scoped, typeof(IAchievementService))]
internal class AchievementService : IAchievementService internal class AchievementService : IAchievementService
{ {
private readonly object saveAchievementLocker = new();
private readonly AppDbContext appDbContext; private readonly AppDbContext appDbContext;
private readonly ILogger<AchievementService> logger; private readonly ILogger<AchievementService> logger;
private readonly DbCurrent<EntityArchive, Message.AchievementArchiveChangedMessage> dbCurrent; private readonly DbCurrent<EntityArchive, Message.AchievementArchiveChangedMessage> dbCurrent;
@@ -187,4 +188,19 @@ internal class AchievementService : IAchievementService
logger.LogInformation(EventIds.Achievement, "{add} added, {update} updated, {remove} removed", result.Add, result.Update, result.Remove); logger.LogInformation(EventIds.Achievement, "{add} added, {update} updated, {remove} removed", result.Add, result.Update, result.Remove);
logger.LogInformation(EventIds.Achievement, "Save achievements for [{name}] completed in {time}ms", name, time); logger.LogInformation(EventIds.Achievement, "Save achievements for [{name}] completed in {time}ms", name, time);
} }
/// <inheritdoc/>
public void SaveAchievement(BindingAchievement achievement)
{
if (achievement.IsChecked)
{
// set to default allow multiple time add
achievement.Entity.InnerId = default;
appDbContext.Achievements.UpdateAndSave(achievement.Entity);
}
else
{
appDbContext.Achievements.RemoveAndSave(achievement.Entity);
}
}
} }

View File

@@ -1,7 +1,6 @@
// 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.Threading.CodeAnalysis;
using Snap.Hutao.Model.InterChange.Achievement; using Snap.Hutao.Model.InterChange.Achievement;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement; using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement;
@@ -57,6 +56,12 @@ internal interface IAchievementService
/// <returns>任务</returns> /// <returns>任务</returns>
Task RemoveArchiveAsync(EntityArchive archive); Task RemoveArchiveAsync(EntityArchive archive);
/// <summary>
/// 保存单个成就
/// </summary>
/// <param name="achievement">成就</param>
void SaveAchievement(BindingAchievement achievement);
/// <summary> /// <summary>
/// 保存成就 /// 保存成就
/// </summary> /// </summary>
@@ -69,6 +74,5 @@ internal interface IAchievementService
/// </summary> /// </summary>
/// <param name="newArchive">新存档</param> /// <param name="newArchive">新存档</param>
/// <returns>存档添加结果</returns> /// <returns>存档添加结果</returns>
[ThreadAccess(ThreadAccessState.AnyThread)]
Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive); Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive);
} }

View File

@@ -148,7 +148,7 @@ internal class CultivationService : ICultivationService
.ToListAsync() .ToListAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
foreach (CultivateEntry? entry in entries) foreach (CultivateEntry entry in entries)
{ {
Guid entryId = entry.InnerId; Guid entryId = entry.InnerId;
@@ -178,6 +178,70 @@ internal class CultivationService : ICultivationService
} }
} }
/// <inheritdoc/>
public async Task<List<Model.Binding.Cultivation.StatisticsCultivateItem>> GetStatisticsCultivateItemsAsync(CultivateProject cultivateProject, List<Model.Metadata.Material> materials)
{
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
Guid projectId = cultivateProject.InnerId;
List<Model.Binding.Cultivation.StatisticsCultivateItem> resultItems = new();
List<CultivateEntry> entries = await appDbContext.CultivateEntries
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
foreach (CultivateEntry entry in entries)
{
Guid entryId = entry.InnerId;
List<CultivateItem> items = await appDbContext.CultivateItems
.AsNoTracking()
.Where(i => i.EntryId == entryId)
.OrderBy(i => i.ItemId)
.ToListAsync()
.ConfigureAwait(false);
foreach (CultivateItem item in items)
{
if (item.IsFinished)
{
continue;
}
if (resultItems.SingleOrDefault(i => i.Inner.Id == item.ItemId) is Model.Binding.Cultivation.StatisticsCultivateItem inPlaceItem)
{
inPlaceItem.Count += item.Count;
}
else
{
resultItems.Add(new(materials.Single(m => m.Id == item.ItemId), item));
}
}
}
List<InventoryItem> inventoryItems = await appDbContext.InventoryItems
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
foreach (InventoryItem inventoryItem in inventoryItems)
{
if (resultItems.SingleOrDefault(i => i.Inner.Id == inventoryItem.ItemId) is Model.Binding.Cultivation.StatisticsCultivateItem inPlaceItem)
{
inPlaceItem.TotalCount += inventoryItem.Count;
}
}
return resultItems.OrderByDescending(i => i.Count).ToList();
}
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task RemoveCultivateEntryAsync(Guid entryId) public async Task RemoveCultivateEntryAsync(Guid entryId)
{ {

View File

@@ -44,6 +44,14 @@ internal interface ICultivationService
/// <returns>项目集合</returns> /// <returns>项目集合</returns>
ObservableCollection<CultivateProject> GetProjectCollection(); ObservableCollection<CultivateProject> GetProjectCollection();
/// <summary>
/// 异步获取统计物品列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="materials">元数据</param>
/// <returns>统计物品列表</returns>
Task<List<StatisticsCultivateItem>> GetStatisticsCultivateItemsAsync(CultivateProject cultivateProject, List<Material> materials);
/// <summary> /// <summary>
/// 删除养成清单 /// 删除养成清单
/// </summary> /// </summary>

View File

@@ -244,7 +244,7 @@ internal class GameService : IGameService, IDisposable
string commandLine = new CommandLineBuilder() string commandLine = new CommandLineBuilder()
.AppendIf("-popupwindow", configuration.IsBorderless) .AppendIf("-popupwindow", configuration.IsBorderless)
.Append("-screen-fullscreen", configuration.IsFullScreen ? 1 : 0) .Append("-screen-fullscreen", configuration.IsFullScreen ? 1 : 0)
.AppendIfNotNull("-window-mode", configuration.WindowMode) .AppendIf("-window-mode", configuration.IsExclusive, "exclusive")
.Append("-screen-width", configuration.ScreenWidth) .Append("-screen-width", configuration.ScreenWidth)
.Append("-screen-height", configuration.ScreenHeight) .Append("-screen-height", configuration.ScreenHeight)
.ToString(); .ToString();

View File

@@ -8,6 +8,11 @@ namespace Snap.Hutao.Service.Game;
/// </summary> /// </summary>
internal readonly struct LaunchConfiguration internal readonly struct LaunchConfiguration
{ {
/// <summary>
/// 是否为独占全屏
/// </summary>
public readonly bool IsExclusive;
/// <summary> /// <summary>
/// 是否全屏,全屏时无边框设置将被覆盖 /// 是否全屏,全屏时无边框设置将被覆盖
/// </summary> /// </summary>
@@ -41,14 +46,16 @@ internal readonly struct LaunchConfiguration
/// <summary> /// <summary>
/// 构造一个新的启动配置 /// 构造一个新的启动配置
/// </summary> /// </summary>
/// <param name="isExclusive">独占全屏</param>
/// <param name="isFullScreen">全屏</param> /// <param name="isFullScreen">全屏</param>
/// <param name="isBorderless">无边框</param> /// <param name="isBorderless">无边框</param>
/// <param name="screenWidth">宽度</param> /// <param name="screenWidth">宽度</param>
/// <param name="screenHeight">高度</param> /// <param name="screenHeight">高度</param>
/// <param name="unlockFps">解锁帧率</param> /// <param name="unlockFps">解锁帧率</param>
/// <param name="targetFps">目标帧率</param> /// <param name="targetFps">目标帧率</param>
public LaunchConfiguration(bool isFullScreen, bool isBorderless, int screenWidth, int screenHeight, bool unlockFps, int targetFps) public LaunchConfiguration(bool isExclusive, bool isFullScreen, bool isBorderless, int screenWidth, int screenHeight, bool unlockFps, int targetFps)
{ {
IsExclusive = isExclusive;
IsFullScreen = isFullScreen; IsFullScreen = isFullScreen;
IsBorderless = isBorderless; IsBorderless = isBorderless;
ScreenHeight = screenHeight; ScreenHeight = screenHeight;
@@ -57,15 +64,4 @@ internal readonly struct LaunchConfiguration
UnlockFPS = unlockFps; UnlockFPS = unlockFps;
TargetFPS = targetFps; TargetFPS = targetFps;
} }
/// <summary>
/// 窗口模式字符串
/// </summary>
public string? WindowMode
{
get
{
return IsFullScreen ? "exclusive" : IsBorderless ? "borderless" : null;
}
}
} }

View File

@@ -139,7 +139,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0-preview2" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0-preview3" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />

View File

@@ -15,6 +15,10 @@
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources>
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
</Page.Resources>
<mxi:Interaction.Behaviors> <mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/> <shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
</mxi:Interaction.Behaviors> </mxi:Interaction.Behaviors>
@@ -77,12 +81,8 @@
Command="{Binding RemoveArchiveCommand}" Command="{Binding RemoveArchiveCommand}"
Icon="{shcm:FontIcon Glyph=&#xE74D;}" Icon="{shcm:FontIcon Glyph=&#xE74D;}"
Label="删除当前存档"/> Label="删除当前存档"/>
<AppBarSeparator/> <AppBarSeparator/>
<AppBarButton
Command="{Binding RefreshFinishPercentCommand}"
Icon="{shcm:FontIcon Glyph=&#xE72C;}"
Label="刷新成就进度"/>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE8B5;}" Label="导入"> <AppBarButton Icon="{shcm:FontIcon Glyph=&#xE8B5;}" Label="导入">
<AppBarButton.Flyout> <AppBarButton.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedRight"> <MenuFlyout Placement="BottomEdgeAlignedRight">
@@ -102,6 +102,7 @@
Icon="{shcm:FontIcon Glyph=&#xEDE1;}" Icon="{shcm:FontIcon Glyph=&#xEDE1;}"
Label="导出"/> Label="导出"/>
<AppBarSeparator/> <AppBarSeparator/>
<AppBarToggleButton <AppBarToggleButton
Command="{Binding SortIncompletedSwitchCommand}" Command="{Binding SortIncompletedSwitchCommand}"
Icon="{shcm:FontIcon Glyph=&#xE8CB;}" Icon="{shcm:FontIcon Glyph=&#xE8CB;}"
@@ -182,6 +183,8 @@
Grid.Column="1" Grid.Column="1"
Margin="6,0,12,0" Margin="6,0,12,0"
Padding="16,0,0,0" Padding="16,0,0,0"
Command="{Binding Path=DataContext.SaveAchievementCommand, Source={StaticResource BindingProxy}}"
CommandParameter="{Binding}"
IsChecked="{Binding IsChecked, Mode=TwoWay}" IsChecked="{Binding IsChecked, Mode=TwoWay}"
Style="{StaticResource DefaultCheckBoxStyle}"> Style="{StaticResource DefaultCheckBoxStyle}">
<CheckBox.Content> <CheckBox.Content>

View File

@@ -49,6 +49,54 @@
<Pivot> <Pivot>
<Pivot.RightHeader> <Pivot.RightHeader>
<CommandBar DefaultLabelPosition="Right"> <CommandBar DefaultLabelPosition="Right">
<AppBarButton
Command="{Binding UpdateStatisticsItemsCommand}"
Icon="{shcm:FontIcon Glyph=&#xEB05;}"
Label="材料统计">
<AppBarButton.Flyout>
<Flyout Placement="Bottom">
<Flyout.FlyoutPresenterStyle>
<Style BasedOn="{StaticResource DefaultFlyoutPresenterStyle}" TargetType="FlyoutPresenter">
<Setter Property="MaxHeight" Value="480"/>
<Setter Property="Padding" Value="0,2,0,2"/>
<Setter Property="Background" Value="{ThemeResource FlyoutPresenterBackground}"/>
</Style>
</Flyout.FlyoutPresenterStyle>
<ItemsControl Margin="16,0,16,16" ItemsSource="{Binding StatisticsItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,16,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="160"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:ItemIcon
Grid.Column="0"
Width="32"
Height="32"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<TextBlock
Grid.Column="1"
Margin="16,0,0,0"
VerticalAlignment="Center"
Text="{Binding Inner.Name}"
TextTrimming="CharacterEllipsis"/>
<TextBlock
Grid.Column="2"
Margin="16,0,4,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{Binding CountFormatted}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarSeparator/>
<AppBarElementContainer> <AppBarElementContainer>
<ComboBox <ComboBox
Height="36" Height="36"

View File

@@ -10,6 +10,7 @@
xmlns:shc="using:Snap.Hutao.Control" xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior" xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shcm="using:Snap.Hutao.Control.Markup" xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shmbh="using:Snap.Hutao.Model.Binding.Hutao"
xmlns:shv="using:Snap.Hutao.ViewModel" xmlns:shv="using:Snap.Hutao.ViewModel"
xmlns:shvc="using:Snap.Hutao.View.Control" xmlns:shvc="using:Snap.Hutao.View.Control"
d:DataContext="{d:DesignInstance shv:HutaoDatabaseViewModel}" d:DataContext="{d:DesignInstance shv:HutaoDatabaseViewModel}"
@@ -20,6 +21,47 @@
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/> <shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
</mxi:Interaction.Behaviors> </mxi:Interaction.Behaviors>
<Page.Resources>
<DataTemplate x:Key="TeamItemTemplate" x:DataType="shmbh:Team">
<Border Margin="12,0,12,12" Style="{StaticResource BorderCardStyle}">
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<ItemsControl HorizontalAlignment="Left" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<cwuc:UniformGrid ColumnSpacing="6" Columns="4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<shvc:ItemIcon
Width="48"
Height="48"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="{Binding Rate}"/>
<TextBlock
Margin="0,2,0,0"
Opacity="0.6"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Name}"/>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</Page.Resources>
<Grid> <Grid>
<Pivot> <Pivot>
<Pivot.RightHeader> <Pivot.RightHeader>
@@ -97,6 +139,39 @@
</Pivot.ItemTemplate> </Pivot.ItemTemplate>
</Pivot> </Pivot>
</PivotItem> </PivotItem>
<PivotItem Header="队伍出场">
<Pivot ItemsSource="{Binding TeamAppearances}">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Floor}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" Margin="0,12,0,0">
<ItemsControl
ItemTemplate="{StaticResource TeamItemTemplate}"
ItemsPanel="{StaticResource ItemsStackPanelTemplate}"
ItemsSource="{Binding Up}"/>
</ScrollViewer>
<ScrollViewer Grid.Column="1" Margin="0,12,0,0">
<ItemsControl
ItemTemplate="{StaticResource TeamItemTemplate}"
ItemsPanel="{StaticResource ItemsStackPanelTemplate}"
ItemsSource="{Binding Down}"/>
</ScrollViewer>
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
</PivotItem>
<PivotItem Header="角色持有"> <PivotItem Header="角色持有">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
@@ -231,117 +306,6 @@
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</PivotItem> </PivotItem>
<PivotItem Header="队伍出场">
<Pivot ItemsSource="{Binding TeamAppearances}">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Floor}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" Margin="0,12,0,0">
<ItemsControl ItemsSource="{Binding Up}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Margin="12,0,12,12"
Background="{StaticResource CardBackgroundFillColorDefault}"
CornerRadius="{StaticResource CompatCornerRadius}">
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<ItemsControl HorizontalAlignment="Left" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<cwuc:UniformGrid ColumnSpacing="6" Columns="4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<shvc:ItemIcon
Width="48"
Height="48"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Rate}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<ScrollViewer Grid.Column="1" Margin="0,12,0,0">
<ItemsControl ItemsSource="{Binding Down}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Margin="12,0,12,12"
Background="{StaticResource CardBackgroundFillColorDefault}"
CornerRadius="{StaticResource CompatCornerRadius}">
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<ItemsControl HorizontalAlignment="Left" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<cwuc:UniformGrid ColumnSpacing="6" Columns="4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<shvc:ItemIcon
Width="48"
Height="48"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Rate}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
</PivotItem>
</Pivot> </Pivot>
</Grid> </Grid>
</shc:ScopedPage> </shc:ScopedPage>

View File

@@ -158,6 +158,17 @@
</sc:SettingExpander> </sc:SettingExpander>
</sc:SettingsGroup> </sc:SettingsGroup>
<sc:SettingsGroup Header="外观"> <sc:SettingsGroup Header="外观">
<sc:Setting
Description="与游戏内浏览器不兼容,切屏等操作也能使游戏闪退"
Header="独占全屏"
Icon="&#xE740;">
<sc:Setting.ActionContent>
<ToggleSwitch
Width="120"
IsOn="{Binding IsExclusive, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchSettingStyle}"/>
</sc:Setting.ActionContent>
</sc:Setting>
<sc:Setting <sc:Setting
Description="覆盖默认的全屏状态" Description="覆盖默认的全屏状态"
Header="全屏" Header="全屏"

View File

@@ -10,6 +10,7 @@ using Snap.Hutao.Control;
using Snap.Hutao.Control.Extension; using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.DataTransfer; using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Threading.CodeAnalysis; using Snap.Hutao.Core.Threading.CodeAnalysis;
using Snap.Hutao.Extension; using Snap.Hutao.Extension;
using Snap.Hutao.Factory.Abstraction; using Snap.Hutao.Factory.Abstraction;
@@ -33,26 +34,22 @@ namespace Snap.Hutao.ViewModel;
/// 成就视图模型 /// 成就视图模型
/// </summary> /// </summary>
[Injection(InjectAs.Scoped)] [Injection(InjectAs.Scoped)]
[SuppressMessage("", "SA1124")]
internal class AchievementViewModel internal class AchievementViewModel
: ObservableObject, : ObservableObject,
ISupportCancellation, ISupportCancellation,
INavigationRecipient, INavigationRecipient
IDisposable,
IRecipient<AchievementArchiveChangedMessage>
{ {
private static readonly SortDescription IncompletedItemsFirstSortDescription = new(nameof(Model.Binding.Achievement.Achievement.IsChecked), SortDirection.Ascending); private static readonly SortDescription IncompletedItemsFirstSortDescription = new(nameof(Model.Binding.Achievement.Achievement.IsChecked), SortDirection.Ascending);
private static readonly SortDescription CompletionTimeSortDescription = new(nameof(Model.Binding.Achievement.Achievement.Time), SortDirection.Descending); private static readonly SortDescription CompletionTimeSortDescription = new(nameof(Model.Binding.Achievement.Achievement.Time), SortDirection.Descending);
private readonly IAchievementService achievementService;
private readonly IMetadataService metadataService; private readonly IMetadataService metadataService;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly JsonSerializerOptions options; private readonly JsonSerializerOptions options;
private readonly IAchievementService achievementService;
private readonly TaskCompletionSource<bool> openUICompletionSource = new(); private readonly TaskCompletionSource<bool> openUICompletionSource = new();
private bool disposed;
private AdvancedCollectionView? achievements; private AdvancedCollectionView? achievements;
private List<Model.Binding.Achievement.AchievementGoal>? achievementGoals; private List<Model.Binding.Achievement.AchievementGoal>? achievementGoals;
private Model.Binding.Achievement.AchievementGoal? selectedAchievementGoal; private Model.Binding.Achievement.AchievementGoal? selectedAchievementGoal;
@@ -94,9 +91,7 @@ internal class AchievementViewModel
RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync); RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync);
SearchAchievementCommand = new RelayCommand<string>(SearchAchievement); SearchAchievementCommand = new RelayCommand<string>(SearchAchievement);
SortIncompletedSwitchCommand = new RelayCommand(UpdateAchievementsSort); SortIncompletedSwitchCommand = new RelayCommand(UpdateAchievementsSort);
RefreshFinishPercentCommand = new RelayCommand(UpdateAchievementFinishPercent); SaveAchievementCommand = new RelayCommand<Model.Binding.Achievement.Achievement>(SaveAchievement);
messenger.Register(this);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -127,6 +122,10 @@ internal class AchievementViewModel
if (SetProperty(ref selectedArchive, value)) if (SetProperty(ref selectedArchive, value))
{ {
achievementService.CurrentArchive = value; achievementService.CurrentArchive = value;
if (value != null)
{
UpdateAchievementsAsync(value).SafeForget();
}
} }
} }
} }
@@ -159,7 +158,7 @@ internal class AchievementViewModel
{ {
SetProperty(ref selectedAchievementGoal, value); SetProperty(ref selectedAchievementGoal, value);
SearchText = string.Empty; SearchText = string.Empty;
UpdateAchievementFilter(value); UpdateAchievementsFilter(value);
} }
} }
@@ -227,36 +226,16 @@ internal class AchievementViewModel
public ICommand SortIncompletedSwitchCommand { get; } public ICommand SortIncompletedSwitchCommand { get; }
/// <summary> /// <summary>
/// 刷新完成百分比命令 /// 保存单个成就命令
/// </summary> /// </summary>
public ICommand RefreshFinishPercentCommand { get; } public ICommand SaveAchievementCommand { get; }
/// <inheritdoc/>
public void Receive(AchievementArchiveChangedMessage message)
{
HandleArchiveChangeAsync(message.OldValue, message.NewValue).SafeForget();
}
/// <inheritdoc/>
public void Dispose()
{
if (!disposed)
{
if (Achievements != null && SelectedArchive != null)
{
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement.Achievement>)!);
}
disposed = true;
}
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> ReceiveAsync(INavigationData data) public async Task<bool> ReceiveAsync(INavigationData data)
{ {
if (await openUICompletionSource.Task.ConfigureAwait(false)) if (await openUICompletionSource.Task.ConfigureAwait(false))
{ {
if (data.Data is "InvokeByUri") if (data.Data is Activation.ImportUIAFFromClipBoard)
{ {
await ImportUIAFFromClipboardAsync().ConfigureAwait(false); await ImportUIAFFromClipboardAsync().ConfigureAwait(false);
return true; return true;
@@ -268,43 +247,32 @@ internal class AchievementViewModel
private static Task<ContentDialogResult> ShowImportResultDialogAsync(string title, string message) private static Task<ContentDialogResult> ShowImportResultDialogAsync(string title, string message)
{ {
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
ContentDialog dialog = new() ContentDialog dialog = new()
{ {
Title = title, Title = title,
Content = message, Content = message,
PrimaryButtonText = "确认", PrimaryButtonText = "确认",
DefaultButton = ContentDialogButton.Primary, DefaultButton = ContentDialogButton.Primary,
XamlRoot = mainWindow.Content.XamlRoot,
}; };
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>(); return dialog.ShowAsync().AsTask();
return dialog.InitializeWithWindow(mainWindow).ShowAsync().AsTask();
} }
private static Task<ContentDialogResult> ShowImportFailDialogAsync(string message) private static Task<ContentDialogResult> ShowImportFailDialogAsync(string message)
{ {
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
ContentDialog dialog = new() ContentDialog dialog = new()
{ {
Title = "导入失败", Title = "导入失败",
Content = message, Content = message,
PrimaryButtonText = "确认", PrimaryButtonText = "确认",
DefaultButton = ContentDialogButton.Primary, DefaultButton = ContentDialogButton.Primary,
XamlRoot = mainWindow.Content.XamlRoot,
}; };
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>(); return dialog.ShowAsync().AsTask();
return dialog.InitializeWithWindow(mainWindow).ShowAsync().AsTask();
}
private async Task HandleArchiveChangeAsync(Model.Entity.AchievementArchive? oldArchieve, Model.Entity.AchievementArchive? newArchieve)
{
if (oldArchieve != null && Achievements != null)
{
achievementService.SaveAchievements(oldArchieve, (Achievements.Source as IList<Model.Binding.Achievement.Achievement>)!);
}
if (newArchieve != null)
{
await UpdateAchievementsAsync(newArchieve).ConfigureAwait(false);
}
} }
private async Task OpenUIAsync() private async Task OpenUIAsync()
@@ -339,20 +307,7 @@ internal class AchievementViewModel
IsInitialized = true; IsInitialized = true;
} }
private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive) #region
{
List<Model.Metadata.Achievement.Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List<Model.Binding.Achievement.Achievement> combined = achievementService.GetAchievements(archive, rawAchievements);
// Assemble achievements on the UI thread.
await ThreadHelper.SwitchToMainThreadAsync();
Achievements = new(combined, true);
UpdateAchievementFinishPercent();
UpdateAchievementFilter(SelectedAchievementGoal);
UpdateAchievementsSort();
}
private async Task AddArchiveAsync() private async Task AddArchiveAsync()
{ {
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>(); MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
@@ -404,7 +359,33 @@ internal class AchievementViewModel
} }
} }
} }
#endregion
private void SearchAchievement(string? search)
{
if (Achievements != null)
{
SetProperty(ref selectedAchievementGoal, null);
if (!string.IsNullOrEmpty(search))
{
if (search.Length == 5 && int.TryParse(search, out int achiId))
{
Achievements.Filter = (object o) => ((Model.Binding.Achievement.Achievement)o).Inner.Id == achiId;
}
else
{
Achievements.Filter = (object o) =>
{
Model.Binding.Achievement.Achievement achi = (Model.Binding.Achievement.Achievement)o;
return achi.Inner.Title.Contains(search) || achi.Inner.Description.Contains(search);
};
}
}
}
}
#region
private async Task ExportAsUIAFToFileAsync() private async Task ExportAsUIAFToFileAsync()
{ {
if (SelectedArchive == null || Achievements == null) if (SelectedArchive == null || Achievements == null)
@@ -413,8 +394,6 @@ internal class AchievementViewModel
return; return;
} }
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement.Achievement>)!);
await ThreadHelper.SwitchToMainThreadAsync(); await ThreadHelper.SwitchToMainThreadAsync();
IPickerFactory pickerFactory = Ioc.Default.GetRequiredService<IPickerFactory>(); IPickerFactory pickerFactory = Ioc.Default.GetRequiredService<IPickerFactory>();
FileSavePicker picker = pickerFactory.GetFileSavePicker(); FileSavePicker picker = pickerFactory.GetFileSavePicker();
@@ -440,30 +419,6 @@ internal class AchievementViewModel
} }
} }
private void SearchAchievement(string? search)
{
if (Achievements != null)
{
SetProperty(ref selectedAchievementGoal, null);
if (!string.IsNullOrEmpty(search))
{
if (search.Length == 5 && int.TryParse(search, out int achiId))
{
Achievements.Filter = (object o) => ((Model.Binding.Achievement.Achievement)o).Inner.Id == achiId;
}
else
{
Achievements.Filter = (object o) =>
{
Model.Binding.Achievement.Achievement achi = (Model.Binding.Achievement.Achievement)o;
return achi.Inner.Title.Contains(search) || achi.Inner.Description.Contains(search);
};
}
}
}
}
private async Task ImportUIAFFromClipboardAsync() private async Task ImportUIAFFromClipboardAsync()
{ {
if (achievementService.CurrentArchive == null) if (achievementService.CurrentArchive == null)
@@ -559,6 +514,21 @@ internal class AchievementViewModel
return false; return false;
} }
#endregion
private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive)
{
List<Model.Metadata.Achievement.Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List<Model.Binding.Achievement.Achievement> combined = achievementService.GetAchievements(archive, rawAchievements);
// Assemble achievements on the UI thread.
await ThreadHelper.SwitchToMainThreadAsync();
Achievements = new(combined, true);
UpdateAchievementsFinishPercent();
UpdateAchievementsFilter(SelectedAchievementGoal);
UpdateAchievementsSort();
}
private void UpdateAchievementsSort() private void UpdateAchievementsSort()
{ {
@@ -576,7 +546,7 @@ internal class AchievementViewModel
} }
} }
private void UpdateAchievementFilter(Model.Binding.Achievement.AchievementGoal? goal) private void UpdateAchievementsFilter(Model.Binding.Achievement.AchievementGoal? goal)
{ {
if (Achievements != null) if (Achievements != null)
{ {
@@ -586,16 +556,16 @@ internal class AchievementViewModel
} }
} }
private void UpdateAchievementFinishPercent() private void UpdateAchievementsFinishPercent()
{ {
int finished = 0; int finished = 0;
int count = 0; int count = 0;
if (Achievements != null && AchievementGoals != null) if (Achievements != null && AchievementGoals != null)
{ {
Dictionary<int, AchievementGoalAggregation> counter = AchievementGoals.ToDictionary(x => x.Id, x => new AchievementGoalAggregation(x)); Dictionary<int, GoalAggregation> counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalAggregation(x));
foreach (Model.Binding.Achievement.Achievement achievement in Achievements.OfType<Model.Binding.Achievement.Achievement>()) foreach (Model.Binding.Achievement.Achievement achievement in Achievements.SourceCollection.OfType<Model.Binding.Achievement.Achievement>())
{ {
ref AchievementGoalAggregation aggregation = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal); ref GoalAggregation aggregation = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
aggregation.Count += 1; aggregation.Count += 1;
count += 1; count += 1;
if (achievement.IsChecked) if (achievement.IsChecked)
@@ -605,7 +575,7 @@ internal class AchievementViewModel
} }
} }
foreach (AchievementGoalAggregation aggregation1 in counter.Values) foreach (GoalAggregation aggregation1 in counter.Values)
{ {
aggregation1.AchievementGoal.UpdateFinishPercent(aggregation1.Finished, aggregation1.Count); aggregation1.AchievementGoal.UpdateFinishPercent(aggregation1.Finished, aggregation1.Count);
} }
@@ -614,13 +584,22 @@ internal class AchievementViewModel
} }
} }
private struct AchievementGoalAggregation private void SaveAchievement(Model.Binding.Achievement.Achievement? achievement)
{
if (achievement != null)
{
achievementService.SaveAchievement(achievement);
UpdateAchievementsFinishPercent();
}
}
private struct GoalAggregation
{ {
public readonly Model.Binding.Achievement.AchievementGoal AchievementGoal; public readonly Model.Binding.Achievement.AchievementGoal AchievementGoal;
public int Finished; public int Finished;
public int Count; public int Count;
public AchievementGoalAggregation(Model.Binding.Achievement.AchievementGoal goal) public GoalAggregation(Model.Binding.Achievement.AchievementGoal goal)
{ {
AchievementGoal = goal; AchievementGoal = goal;
} }

View File

@@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Control; using Snap.Hutao.Control;
using Snap.Hutao.Factory.Abstraction; using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Message; using Snap.Hutao.Message;
using Snap.Hutao.Model.Binding.Cultivation;
using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Cultivation; using Snap.Hutao.Service.Cultivation;
@@ -20,7 +21,7 @@ namespace Snap.Hutao.ViewModel;
/// 养成视图模型 /// 养成视图模型
/// </summary> /// </summary>
[Injection(InjectAs.Scoped)] [Injection(InjectAs.Scoped)]
internal class CultivationViewModel : ObservableObject, ISupportCancellation, IRecipient<CultivateProjectChangedMessage> internal class CultivationViewModel : ObservableObject, ISupportCancellation
{ {
private readonly ICultivationService cultivationService; private readonly ICultivationService cultivationService;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
@@ -31,6 +32,7 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
private CultivateProject? selectedProject; private CultivateProject? selectedProject;
private List<Model.Binding.Inventory.InventoryItem>? inventoryItems; private List<Model.Binding.Inventory.InventoryItem>? inventoryItems;
private ObservableCollection<Model.Binding.Cultivation.CultivateEntry>? cultivateEntries; private ObservableCollection<Model.Binding.Cultivation.CultivateEntry>? cultivateEntries;
private List<StatisticsCultivateItem>? statisticsItems;
/// <summary> /// <summary>
/// 构造一个新的养成视图模型 /// 构造一个新的养成视图模型
@@ -40,14 +42,12 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
/// <param name="asyncRelayCommandFactory">异步命令工厂</param> /// <param name="asyncRelayCommandFactory">异步命令工厂</param>
/// <param name="metadataService">元数据服务</param> /// <param name="metadataService">元数据服务</param>
/// <param name="logger">日志器</param> /// <param name="logger">日志器</param>
/// <param name="messenger">消息器</param>
public CultivationViewModel( public CultivationViewModel(
ICultivationService cultivationService, ICultivationService cultivationService,
IInfoBarService infoBarService, IInfoBarService infoBarService,
IAsyncRelayCommandFactory asyncRelayCommandFactory, IAsyncRelayCommandFactory asyncRelayCommandFactory,
IMetadataService metadataService, IMetadataService metadataService,
ILogger<CultivationViewModel> logger, ILogger<CultivationViewModel> logger)
IMessenger messenger)
{ {
this.cultivationService = cultivationService; this.cultivationService = cultivationService;
this.infoBarService = infoBarService; this.infoBarService = infoBarService;
@@ -59,8 +59,7 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
RemoveProjectCommand = asyncRelayCommandFactory.Create<CultivateProject>(RemoveProjectAsync); RemoveProjectCommand = asyncRelayCommandFactory.Create<CultivateProject>(RemoveProjectAsync);
RemoveEntryCommand = asyncRelayCommandFactory.Create<Model.Binding.Cultivation.CultivateEntry>(RemoveEntryAsync); RemoveEntryCommand = asyncRelayCommandFactory.Create<Model.Binding.Cultivation.CultivateEntry>(RemoveEntryAsync);
SaveInventoryItemCommand = new RelayCommand<Model.Binding.Inventory.InventoryItem>(SaveInventoryItem); SaveInventoryItemCommand = new RelayCommand<Model.Binding.Inventory.InventoryItem>(SaveInventoryItem);
UpdateStatisticsItemsCommand = asyncRelayCommandFactory.Create(UpdateStatisticsItemsAsync);
messenger.Register(this);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -81,6 +80,10 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
if (SetProperty(ref selectedProject, value)) if (SetProperty(ref selectedProject, value))
{ {
cultivationService.Current = value; cultivationService.Current = value;
if (value != null)
{
UpdateCultivateEntriesAndInventoryItemsAsync(value).SafeForget(logger);
}
} }
} }
} }
@@ -95,6 +98,11 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
/// </summary> /// </summary>
public ObservableCollection<Model.Binding.Cultivation.CultivateEntry>? CultivateEntries { get => cultivateEntries; set => SetProperty(ref cultivateEntries, value); } public ObservableCollection<Model.Binding.Cultivation.CultivateEntry>? CultivateEntries { get => cultivateEntries; set => SetProperty(ref cultivateEntries, value); }
/// <summary>
/// 统计列表
/// </summary>
public List<StatisticsCultivateItem>? StatisticsItems { get => statisticsItems; set => SetProperty(ref statisticsItems, value); }
/// <summary> /// <summary>
/// 打开界面命令 /// 打开界面命令
/// </summary> /// </summary>
@@ -120,11 +128,10 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
/// </summary> /// </summary>
public ICommand SaveInventoryItemCommand { get; } public ICommand SaveInventoryItemCommand { get; }
/// <inheritdoc/> /// <summary>
public void Receive(CultivateProjectChangedMessage message) /// 展示统计物品命令
{ /// </summary>
UpdateCultivateEntriesAndInventoryItemsAsync(message.NewValue).SafeForget(logger); public ICommand UpdateStatisticsItemsCommand { get; }
}
private async Task OpenUIAsync() private async Task OpenUIAsync()
{ {
@@ -209,4 +216,18 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
cultivationService.SaveInventoryItem(inventoryItem); cultivationService.SaveInventoryItem(inventoryItem);
} }
} }
private async Task UpdateStatisticsItemsAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(true))
{
if (SelectedProject != null)
{
List<Model.Metadata.Material> materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false);
List<StatisticsCultivateItem> temp = await cultivationService.GetStatisticsCultivateItemsAsync(SelectedProject, materials).ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
StatisticsItems = temp;
}
}
}
} }

View File

@@ -51,6 +51,7 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
private LaunchScheme? selectedScheme; private LaunchScheme? selectedScheme;
private ObservableCollection<GameAccount>? gameAccounts; private ObservableCollection<GameAccount>? gameAccounts;
private GameAccount? selectedGameAccount; private GameAccount? selectedGameAccount;
private bool isExclusive;
private bool isFullScreen; private bool isFullScreen;
private bool isBorderless; private bool isBorderless;
private int screenWidth; private int screenWidth;
@@ -106,15 +107,57 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
/// </summary> /// </summary>
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); } public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
/// <summary>
/// 是否为独占全屏
/// </summary>
public bool IsExclusive
{
get => isExclusive; set
{
if (SetProperty(ref isExclusive, value))
{
if (value)
{
IsFullScreen = true;
}
}
}
}
/// <summary> /// <summary>
/// 全屏 /// 全屏
/// </summary> /// </summary>
public bool IsFullScreen { get => isFullScreen; set => SetProperty(ref isFullScreen, value); } public bool IsFullScreen
{
get => isFullScreen; set
{
if (SetProperty(ref isFullScreen, value))
{
if (value)
{
IsBorderless = false;
}
}
}
}
/// <summary> /// <summary>
/// 无边框 /// 无边框
/// </summary> /// </summary>
public bool IsBorderless { get => isBorderless; set => SetProperty(ref isBorderless, value); } public bool IsBorderless
{
get => isBorderless; set
{
if (SetProperty(ref isBorderless, value))
{
if (value)
{
IsExclusive = false;
IsFullScreen = false;
}
}
}
}
/// <summary> /// <summary>
/// 宽度 /// 宽度
@@ -270,7 +313,7 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
SaveSetting(); SaveSetting();
LaunchConfiguration configuration = new(IsFullScreen, IsBorderless, ScreenWidth, ScreenHeight, IsElevated && UnlockFps, TargetFps); LaunchConfiguration configuration = new(IsExclusive, IsFullScreen, IsBorderless, ScreenWidth, ScreenHeight, IsElevated && UnlockFps, TargetFps);
await gameService.LaunchAsync(configuration).ConfigureAwait(false); await gameService.LaunchAsync(configuration).ConfigureAwait(false);
} }

View File

@@ -59,6 +59,11 @@ internal class UserViewModel : ObservableObject
if (SetProperty(ref selectedUser, value)) if (SetProperty(ref selectedUser, value))
{ {
userService.Current = value; userService.Current = value;
if (value != null)
{
value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
}
} }
} }
} }

View File

@@ -40,10 +40,10 @@ internal class PatchClient
} }
/// <summary> /// <summary>
/// 更新信息 /// 异步获取更新信息
/// </summary> /// </summary>
/// <param name="token"></param> /// <param name="token">取消令牌</param>
/// <returns></returns> /// <returns>更新信息</returns>
public Task<Patch.PatchInformation?> GetPatchInformationAsync(CancellationToken token = default) public Task<Patch.PatchInformation?> GetPatchInformationAsync(CancellationToken token = default)
{ {
return httpClient.TryCatchGetFromJsonAsync<Patch.PatchInformation>(ApiEndpoints.PatcherHutaoStable, options, logger, token); return httpClient.TryCatchGetFromJsonAsync<Patch.PatchInformation>(ApiEndpoints.PatcherHutaoStable, options, logger, token);