mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
cultivation improvement
This commit is contained in:
@@ -23,6 +23,11 @@ internal static class Activation
|
||||
/// </summary>
|
||||
public const string LaunchGame = "LaunchGame";
|
||||
|
||||
/// <summary>
|
||||
/// 从剪贴板导入成就
|
||||
/// </summary>
|
||||
public const string ImportUIAFFromClipBoard = "ImportUIAFFromClipBoard";
|
||||
|
||||
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
|
||||
|
||||
/// <summary>
|
||||
@@ -180,7 +185,7 @@ internal static class Activation
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
|
||||
INavigationAwaiter navigationAwaiter = new NavigationExtra("InvokeByUri");
|
||||
INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipBoard);
|
||||
await Ioc.Default
|
||||
.GetRequiredService<INavigationService>()
|
||||
.NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true)
|
||||
|
||||
@@ -65,6 +65,11 @@ public class CultivateItem : ObservableObject
|
||||
/// </summary>
|
||||
public bool IsToday { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 对应背包物品的个数
|
||||
/// </summary>
|
||||
public uint InventoryItemCount { get; set; }
|
||||
|
||||
private void FlipIsFinished()
|
||||
{
|
||||
IsFinished = !IsFinished;
|
||||
|
||||
@@ -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}"; }
|
||||
}
|
||||
@@ -28,8 +28,14 @@ internal class Team : List<ComplexAvatar>
|
||||
}
|
||||
|
||||
Rate = $"上场 {team.Rate} 次";
|
||||
Name = TeamPopularNameParser.GetName(ids.ToHashSet());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 队伍俗名
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上场次数
|
||||
/// </summary>
|
||||
|
||||
@@ -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 "尚未命名";
|
||||
}
|
||||
}
|
||||
@@ -74,24 +74,24 @@ public enum WeaponType
|
||||
/// <summary>
|
||||
/// 法器
|
||||
/// </summary>
|
||||
[Description("单手剑")]
|
||||
[Description("法器")]
|
||||
WEAPON_CATALYST = 10,
|
||||
|
||||
/// <summary>
|
||||
/// 双手剑
|
||||
/// </summary>
|
||||
[Description("单手剑")]
|
||||
[Description("双手剑")]
|
||||
WEAPON_CLAYMORE = 11,
|
||||
|
||||
/// <summary>
|
||||
/// 弓
|
||||
/// </summary>
|
||||
[Description("单手剑")]
|
||||
[Description("弓")]
|
||||
WEAPON_BOW = 12,
|
||||
|
||||
/// <summary>
|
||||
/// 长柄武器
|
||||
/// </summary>
|
||||
[Description("单手剑")]
|
||||
[Description("长柄武器")]
|
||||
WEAPON_POLE = 13,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.2.16.0" />
|
||||
Version="1.2.19.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Snap.Hutao.Service.Achievement;
|
||||
[Injection(InjectAs.Scoped, typeof(IAchievementService))]
|
||||
internal class AchievementService : IAchievementService
|
||||
{
|
||||
private readonly object saveAchievementLocker = new();
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly ILogger<AchievementService> logger;
|
||||
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, "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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Threading.CodeAnalysis;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement;
|
||||
@@ -57,6 +56,12 @@ internal interface IAchievementService
|
||||
/// <returns>任务</returns>
|
||||
Task RemoveArchiveAsync(EntityArchive archive);
|
||||
|
||||
/// <summary>
|
||||
/// 保存单个成就
|
||||
/// </summary>
|
||||
/// <param name="achievement">成就</param>
|
||||
void SaveAchievement(BindingAchievement achievement);
|
||||
|
||||
/// <summary>
|
||||
/// 保存成就
|
||||
/// </summary>
|
||||
@@ -69,6 +74,5 @@ internal interface IAchievementService
|
||||
/// </summary>
|
||||
/// <param name="newArchive">新存档</param>
|
||||
/// <returns>存档添加结果</returns>
|
||||
[ThreadAccess(ThreadAccessState.AnyThread)]
|
||||
Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive);
|
||||
}
|
||||
@@ -148,7 +148,7 @@ internal class CultivationService : ICultivationService
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
foreach (CultivateEntry? entry in entries)
|
||||
foreach (CultivateEntry entry in entries)
|
||||
{
|
||||
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/>
|
||||
public async Task RemoveCultivateEntryAsync(Guid entryId)
|
||||
{
|
||||
|
||||
@@ -44,6 +44,14 @@ internal interface ICultivationService
|
||||
/// <returns>项目集合</returns>
|
||||
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>
|
||||
|
||||
@@ -244,7 +244,7 @@ internal class GameService : IGameService, IDisposable
|
||||
string commandLine = new CommandLineBuilder()
|
||||
.AppendIf("-popupwindow", configuration.IsBorderless)
|
||||
.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-height", configuration.ScreenHeight)
|
||||
.ToString();
|
||||
|
||||
@@ -8,6 +8,11 @@ namespace Snap.Hutao.Service.Game;
|
||||
/// </summary>
|
||||
internal readonly struct LaunchConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为独占全屏
|
||||
/// </summary>
|
||||
public readonly bool IsExclusive;
|
||||
|
||||
/// <summary>
|
||||
/// 是否全屏,全屏时无边框设置将被覆盖
|
||||
/// </summary>
|
||||
@@ -41,14 +46,16 @@ internal readonly struct LaunchConfiguration
|
||||
/// <summary>
|
||||
/// 构造一个新的启动配置
|
||||
/// </summary>
|
||||
/// <param name="isExclusive">独占全屏</param>
|
||||
/// <param name="isFullScreen">全屏</param>
|
||||
/// <param name="isBorderless">无边框</param>
|
||||
/// <param name="screenWidth">宽度</param>
|
||||
/// <param name="screenHeight">高度</param>
|
||||
/// <param name="unlockFps">解锁帧率</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;
|
||||
IsBorderless = isBorderless;
|
||||
ScreenHeight = screenHeight;
|
||||
@@ -57,15 +64,4 @@ internal readonly struct LaunchConfiguration
|
||||
UnlockFPS = unlockFps;
|
||||
TargetFPS = targetFps;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口模式字符串
|
||||
/// </summary>
|
||||
public string? WindowMode
|
||||
{
|
||||
get
|
||||
{
|
||||
return IsFullScreen ? "exclusive" : IsBorderless ? "borderless" : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@
|
||||
</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.UI.Behaviors" Version="7.1.2" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
|
||||
</Page.Resources>
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
@@ -77,12 +81,8 @@
|
||||
Command="{Binding RemoveArchiveCommand}"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="删除当前存档"/>
|
||||
|
||||
<AppBarSeparator/>
|
||||
<AppBarButton
|
||||
Command="{Binding RefreshFinishPercentCommand}"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="刷新成就进度"/>
|
||||
|
||||
<AppBarButton Icon="{shcm:FontIcon Glyph=}" Label="导入">
|
||||
<AppBarButton.Flyout>
|
||||
<MenuFlyout Placement="BottomEdgeAlignedRight">
|
||||
@@ -102,6 +102,7 @@
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="导出"/>
|
||||
<AppBarSeparator/>
|
||||
|
||||
<AppBarToggleButton
|
||||
Command="{Binding SortIncompletedSwitchCommand}"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
@@ -182,6 +183,8 @@
|
||||
Grid.Column="1"
|
||||
Margin="6,0,12,0"
|
||||
Padding="16,0,0,0"
|
||||
Command="{Binding Path=DataContext.SaveAchievementCommand, Source={StaticResource BindingProxy}}"
|
||||
CommandParameter="{Binding}"
|
||||
IsChecked="{Binding IsChecked, Mode=TwoWay}"
|
||||
Style="{StaticResource DefaultCheckBoxStyle}">
|
||||
<CheckBox.Content>
|
||||
|
||||
@@ -49,6 +49,54 @@
|
||||
<Pivot>
|
||||
<Pivot.RightHeader>
|
||||
<CommandBar DefaultLabelPosition="Right">
|
||||
<AppBarButton
|
||||
Command="{Binding UpdateStatisticsItemsCommand}"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
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>
|
||||
<ComboBox
|
||||
Height="36"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shmbh="using:Snap.Hutao.Model.Binding.Hutao"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
d:DataContext="{d:DesignInstance shv:HutaoDatabaseViewModel}"
|
||||
@@ -20,6 +21,47 @@
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</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>
|
||||
<Pivot>
|
||||
<Pivot.RightHeader>
|
||||
@@ -97,6 +139,39 @@
|
||||
</Pivot.ItemTemplate>
|
||||
</Pivot>
|
||||
</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="角色持有">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -231,117 +306,6 @@
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</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>
|
||||
</Grid>
|
||||
</shc:ScopedPage>
|
||||
|
||||
@@ -158,6 +158,17 @@
|
||||
</sc:SettingExpander>
|
||||
</sc:SettingsGroup>
|
||||
<sc:SettingsGroup Header="外观">
|
||||
<sc:Setting
|
||||
Description="与游戏内浏览器不兼容,切屏等操作也能使游戏闪退"
|
||||
Header="独占全屏"
|
||||
Icon="">
|
||||
<sc:Setting.ActionContent>
|
||||
<ToggleSwitch
|
||||
Width="120"
|
||||
IsOn="{Binding IsExclusive, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchSettingStyle}"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
<sc:Setting
|
||||
Description="覆盖默认的全屏状态"
|
||||
Header="全屏"
|
||||
|
||||
@@ -10,6 +10,7 @@ using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.Threading.CodeAnalysis;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
@@ -33,26 +34,22 @@ namespace Snap.Hutao.ViewModel;
|
||||
/// 成就视图模型
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Scoped)]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal class AchievementViewModel
|
||||
: ObservableObject,
|
||||
ISupportCancellation,
|
||||
INavigationRecipient,
|
||||
IDisposable,
|
||||
IRecipient<AchievementArchiveChangedMessage>
|
||||
INavigationRecipient
|
||||
{
|
||||
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 readonly IAchievementService achievementService;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions options;
|
||||
|
||||
private readonly IAchievementService achievementService;
|
||||
|
||||
private readonly TaskCompletionSource<bool> openUICompletionSource = new();
|
||||
|
||||
private bool disposed;
|
||||
|
||||
private AdvancedCollectionView? achievements;
|
||||
private List<Model.Binding.Achievement.AchievementGoal>? achievementGoals;
|
||||
private Model.Binding.Achievement.AchievementGoal? selectedAchievementGoal;
|
||||
@@ -94,9 +91,7 @@ internal class AchievementViewModel
|
||||
RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync);
|
||||
SearchAchievementCommand = new RelayCommand<string>(SearchAchievement);
|
||||
SortIncompletedSwitchCommand = new RelayCommand(UpdateAchievementsSort);
|
||||
RefreshFinishPercentCommand = new RelayCommand(UpdateAchievementFinishPercent);
|
||||
|
||||
messenger.Register(this);
|
||||
SaveAchievementCommand = new RelayCommand<Model.Binding.Achievement.Achievement>(SaveAchievement);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -127,6 +122,10 @@ internal class AchievementViewModel
|
||||
if (SetProperty(ref selectedArchive, value))
|
||||
{
|
||||
achievementService.CurrentArchive = value;
|
||||
if (value != null)
|
||||
{
|
||||
UpdateAchievementsAsync(value).SafeForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,7 +158,7 @@ internal class AchievementViewModel
|
||||
{
|
||||
SetProperty(ref selectedAchievementGoal, value);
|
||||
SearchText = string.Empty;
|
||||
UpdateAchievementFilter(value);
|
||||
UpdateAchievementsFilter(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,36 +226,16 @@ internal class AchievementViewModel
|
||||
public ICommand SortIncompletedSwitchCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 刷新完成百分比命令
|
||||
/// 保存单个成就命令
|
||||
/// </summary>
|
||||
public ICommand RefreshFinishPercentCommand { 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;
|
||||
}
|
||||
}
|
||||
public ICommand SaveAchievementCommand { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> ReceiveAsync(INavigationData data)
|
||||
{
|
||||
if (await openUICompletionSource.Task.ConfigureAwait(false))
|
||||
{
|
||||
if (data.Data is "InvokeByUri")
|
||||
if (data.Data is Activation.ImportUIAFFromClipBoard)
|
||||
{
|
||||
await ImportUIAFFromClipboardAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
@@ -268,43 +247,32 @@ internal class AchievementViewModel
|
||||
|
||||
private static Task<ContentDialogResult> ShowImportResultDialogAsync(string title, string message)
|
||||
{
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
Title = title,
|
||||
Content = message,
|
||||
PrimaryButtonText = "确认",
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
XamlRoot = mainWindow.Content.XamlRoot,
|
||||
};
|
||||
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
return dialog.InitializeWithWindow(mainWindow).ShowAsync().AsTask();
|
||||
return dialog.ShowAsync().AsTask();
|
||||
}
|
||||
|
||||
private static Task<ContentDialogResult> ShowImportFailDialogAsync(string message)
|
||||
{
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
Title = "导入失败",
|
||||
Content = message,
|
||||
PrimaryButtonText = "确认",
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
XamlRoot = mainWindow.Content.XamlRoot,
|
||||
};
|
||||
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
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);
|
||||
}
|
||||
return dialog.ShowAsync().AsTask();
|
||||
}
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
@@ -339,20 +307,7 @@ internal class AchievementViewModel
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
UpdateAchievementFinishPercent();
|
||||
UpdateAchievementFilter(SelectedAchievementGoal);
|
||||
UpdateAchievementsSort();
|
||||
}
|
||||
|
||||
#region 存档操作
|
||||
private async Task AddArchiveAsync()
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (SelectedArchive == null || Achievements == null)
|
||||
@@ -413,8 +394,6 @@ internal class AchievementViewModel
|
||||
return;
|
||||
}
|
||||
|
||||
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement.Achievement>)!);
|
||||
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
IPickerFactory pickerFactory = Ioc.Default.GetRequiredService<IPickerFactory>();
|
||||
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()
|
||||
{
|
||||
if (achievementService.CurrentArchive == null)
|
||||
@@ -559,6 +514,21 @@ internal class AchievementViewModel
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -586,16 +556,16 @@ internal class AchievementViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAchievementFinishPercent()
|
||||
private void UpdateAchievementsFinishPercent()
|
||||
{
|
||||
int finished = 0;
|
||||
int count = 0;
|
||||
if (Achievements != null && AchievementGoals != null)
|
||||
{
|
||||
Dictionary<int, AchievementGoalAggregation> counter = AchievementGoals.ToDictionary(x => x.Id, x => new AchievementGoalAggregation(x));
|
||||
foreach (Model.Binding.Achievement.Achievement achievement in Achievements.OfType<Model.Binding.Achievement.Achievement>())
|
||||
Dictionary<int, GoalAggregation> counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalAggregation(x));
|
||||
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;
|
||||
count += 1;
|
||||
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);
|
||||
}
|
||||
@@ -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 int Finished;
|
||||
public int Count;
|
||||
|
||||
public AchievementGoalAggregation(Model.Binding.Achievement.AchievementGoal goal)
|
||||
public GoalAggregation(Model.Binding.Achievement.AchievementGoal goal)
|
||||
{
|
||||
AchievementGoal = goal;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Message;
|
||||
using Snap.Hutao.Model.Binding.Cultivation;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Cultivation;
|
||||
@@ -20,7 +21,7 @@ namespace Snap.Hutao.ViewModel;
|
||||
/// 养成视图模型
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal class CultivationViewModel : ObservableObject, ISupportCancellation, IRecipient<CultivateProjectChangedMessage>
|
||||
internal class CultivationViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
private readonly ICultivationService cultivationService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
@@ -31,6 +32,7 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
|
||||
private CultivateProject? selectedProject;
|
||||
private List<Model.Binding.Inventory.InventoryItem>? inventoryItems;
|
||||
private ObservableCollection<Model.Binding.Cultivation.CultivateEntry>? cultivateEntries;
|
||||
private List<StatisticsCultivateItem>? statisticsItems;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的养成视图模型
|
||||
@@ -40,14 +42,12 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
/// <param name="metadataService">元数据服务</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public CultivationViewModel(
|
||||
ICultivationService cultivationService,
|
||||
IInfoBarService infoBarService,
|
||||
IAsyncRelayCommandFactory asyncRelayCommandFactory,
|
||||
IMetadataService metadataService,
|
||||
ILogger<CultivationViewModel> logger,
|
||||
IMessenger messenger)
|
||||
ILogger<CultivationViewModel> logger)
|
||||
{
|
||||
this.cultivationService = cultivationService;
|
||||
this.infoBarService = infoBarService;
|
||||
@@ -59,8 +59,7 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
|
||||
RemoveProjectCommand = asyncRelayCommandFactory.Create<CultivateProject>(RemoveProjectAsync);
|
||||
RemoveEntryCommand = asyncRelayCommandFactory.Create<Model.Binding.Cultivation.CultivateEntry>(RemoveEntryAsync);
|
||||
SaveInventoryItemCommand = new RelayCommand<Model.Binding.Inventory.InventoryItem>(SaveInventoryItem);
|
||||
|
||||
messenger.Register(this);
|
||||
UpdateStatisticsItemsCommand = asyncRelayCommandFactory.Create(UpdateStatisticsItemsAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -81,6 +80,10 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
|
||||
if (SetProperty(ref selectedProject, value))
|
||||
{
|
||||
cultivationService.Current = value;
|
||||
if (value != null)
|
||||
{
|
||||
UpdateCultivateEntriesAndInventoryItemsAsync(value).SafeForget(logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +98,11 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
|
||||
/// </summary>
|
||||
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>
|
||||
@@ -120,11 +128,10 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
|
||||
/// </summary>
|
||||
public ICommand SaveInventoryItemCommand { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Receive(CultivateProjectChangedMessage message)
|
||||
{
|
||||
UpdateCultivateEntriesAndInventoryItemsAsync(message.NewValue).SafeForget(logger);
|
||||
}
|
||||
/// <summary>
|
||||
/// 展示统计物品命令
|
||||
/// </summary>
|
||||
public ICommand UpdateStatisticsItemsCommand { get; }
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
@@ -209,4 +216,18 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
private LaunchScheme? selectedScheme;
|
||||
private ObservableCollection<GameAccount>? gameAccounts;
|
||||
private GameAccount? selectedGameAccount;
|
||||
private bool isExclusive;
|
||||
private bool isFullScreen;
|
||||
private bool isBorderless;
|
||||
private int screenWidth;
|
||||
@@ -106,15 +107,57 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
/// </summary>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
/// 宽度
|
||||
@@ -270,7 +313,7 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,11 @@ internal class UserViewModel : ObservableObject
|
||||
if (SetProperty(ref selectedUser, value))
|
||||
{
|
||||
userService.Current = value;
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ internal class PatchClient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新信息
|
||||
/// 异步获取更新信息
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>更新信息</returns>
|
||||
public Task<Patch.PatchInformation?> GetPatchInformationAsync(CancellationToken token = default)
|
||||
{
|
||||
return httpClient.TryCatchGetFromJsonAsync<Patch.PatchInformation>(ApiEndpoints.PatcherHutaoStable, options, logger, token);
|
||||
|
||||
Reference in New Issue
Block a user