diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
index a6519389..6449ff8d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
@@ -23,6 +23,11 @@ internal static class Activation
///
public const string LaunchGame = "LaunchGame";
+ ///
+ /// 从剪贴板导入成就
+ ///
+ public const string ImportUIAFFromClipBoard = "ImportUIAFFromClipBoard";
+
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
///
@@ -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()
.NavigateAsync(navigationAwaiter, true)
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateItem.cs
index 74d56fca..44b5c7fa 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateItem.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateItem.cs
@@ -65,6 +65,11 @@ public class CultivateItem : ObservableObject
///
public bool IsToday { get; }
+ ///
+ /// 对应背包物品的个数
+ ///
+ public uint InventoryItemCount { get; set; }
+
private void FlipIsFinished()
{
IsFinished = !IsFinished;
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/StatisticsCultivateItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/StatisticsCultivateItem.cs
new file mode 100644
index 00000000..e4ad85f8
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/StatisticsCultivateItem.cs
@@ -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;
+
+///
+/// 仅用于统计总数的养成物品
+///
+public class StatisticsCultivateItem
+{
+ ///
+ /// 构造一个新的统计用养成物品
+ ///
+ /// 材料
+ /// 实体
+ public StatisticsCultivateItem(Material inner, Entity.CultivateItem entity)
+ {
+ Inner = inner;
+ Count = entity.Count;
+ }
+
+ ///
+ /// 元数据
+ ///
+ public Material Inner { get; }
+
+ ///
+ /// 对应背包物品的个数
+ ///
+ public int Count { get; set; }
+
+ ///
+ /// 对应背包物品的个数
+ ///
+ public uint TotalCount { get; set; }
+
+ ///
+ /// 是否完成
+ ///
+ public bool IsFinished { get => Count >= TotalCount; }
+
+ ///
+ /// 格式化总数
+ ///
+ public string CountFormatted { get => $"{Count}/{TotalCount}"; }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs
index 3d94fe4f..672cf831 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/Team.cs
@@ -28,8 +28,14 @@ internal class Team : List
}
Rate = $"上场 {team.Rate} 次";
+ Name = TeamPopularNameParser.GetName(ids.ToHashSet());
}
+ ///
+ /// 队伍俗名
+ ///
+ public string Name { get; set; }
+
///
/// 上场次数
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/TeamPopularNameParser.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/TeamPopularNameParser.cs
new file mode 100644
index 00000000..a598a776
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Hutao/TeamPopularNameParser.cs
@@ -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;
+
+///
+/// 队伍俗名解析器
+///
+internal static class TeamPopularNameParser
+{
+ ///
+ /// 已知的队伍名称
+ ///
+ private static readonly ImmutableDictionary> KnownTeamNames = new Dictionary>()
+ {
+ ["雷国|雷行香班"] = new HashSet() { AvatarIds.Shougun, AvatarIds.Xingqiu, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
+ ["雷国|雷夜香班"] = new HashSet() { AvatarIds.Shougun, AvatarIds.Yelan, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
+ ["雷国|雷万香班"] = new HashSet() { AvatarIds.Shougun, AvatarIds.Kazuha, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
+ ["雷九|雷九万班"] = new HashSet() { AvatarIds.Shougun, AvatarIds.Sara, AvatarIds.Kazuha, AvatarIds.Bennett, }.ToImmutableHashSet(),
+ ["万达国际"] = new HashSet() { AvatarIds.Kazuha, AvatarIds.Tartaglia, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
+ ["胡行钟夜"] = new HashSet() { AvatarIds.Hutao, AvatarIds.Xingqiu, AvatarIds.Zhongli, AvatarIds.Yelan, }.ToImmutableHashSet(),
+ ["激晴|刻皇万妲"] = new HashSet() { AvatarIds.Keqing, AvatarIds.Fischl, AvatarIds.Kazuha, AvatarIds.Nahida, }.ToImmutableHashSet(),
+ ["永冻|神鹤万心"] = new HashSet() { AvatarIds.Ayaka, AvatarIds.Shenhe, AvatarIds.Kazuha, AvatarIds.Kokomi, }.ToImmutableHashSet(),
+ ["永冻|神钟万心"] = new HashSet() { AvatarIds.Ayaka, AvatarIds.Zhongli, AvatarIds.Kazuha, AvatarIds.Kokomi, }.ToImmutableHashSet(),
+ ["融甘|融化甘雨"] = new HashSet() { AvatarIds.Ganyu, AvatarIds.Zhongli, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
+ ["妮绽放|女主"] = new HashSet() { AvatarIds.Nilou, AvatarIds.Kokomi, AvatarIds.Nahida, AvatarIds.PlayerGirl, }.ToImmutableHashSet(),
+ ["妮绽放|男主"] = new HashSet() { AvatarIds.Nilou, AvatarIds.Kokomi, AvatarIds.Nahida, AvatarIds.PlayerBoy, }.ToImmutableHashSet(),
+ ["一斗岩队"] = new HashSet() { AvatarIds.Itto, AvatarIds.Albedo, AvatarIds.Zhongli, AvatarIds.Gorou, }.ToImmutableHashSet(),
+ }.ToImmutableDictionary();
+
+ ///
+ /// 获取队伍名称
+ ///
+ /// 队伍集
+ /// 队伍名称
+ public static string GetName(HashSet ids)
+ {
+ foreach (KeyValuePair> entry in KnownTeamNames)
+ {
+ if (entry.Value.SetEquals(ids))
+ {
+ return entry.Key;
+ }
+ }
+
+ return "尚未命名";
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/WeaponType.cs b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/WeaponType.cs
index a0e594d0..da1e0de6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/WeaponType.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/WeaponType.cs
@@ -74,24 +74,24 @@ public enum WeaponType
///
/// 法器
///
- [Description("单手剑")]
+ [Description("法器")]
WEAPON_CATALYST = 10,
///
/// 双手剑
///
- [Description("单手剑")]
+ [Description("双手剑")]
WEAPON_CLAYMORE = 11,
///
/// 弓
///
- [Description("单手剑")]
+ [Description("弓")]
WEAPON_BOW = 12,
///
/// 长柄武器
///
- [Description("单手剑")]
+ [Description("长柄武器")]
WEAPON_POLE = 13,
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
index fd6073a2..a0adddf2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
+++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest
@@ -12,7 +12,7 @@
+ Version="1.2.19.0" />
胡桃
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
index 98a2c640..f26cf7c0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
@@ -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 logger;
private readonly DbCurrent 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);
}
+
+ ///
+ 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);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs
index a2bae36f..45da7456 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs
@@ -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
/// 任务
Task RemoveArchiveAsync(EntityArchive archive);
+ ///
+ /// 保存单个成就
+ ///
+ /// 成就
+ void SaveAchievement(BindingAchievement achievement);
+
///
/// 保存成就
///
@@ -69,6 +74,5 @@ internal interface IAchievementService
///
/// 新存档
/// 存档添加结果
- [ThreadAccess(ThreadAccessState.AnyThread)]
Task TryAddArchiveAsync(EntityArchive newArchive);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
index 95bb831f..25b48ad3 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
@@ -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
}
}
+ ///
+ public async Task> GetStatisticsCultivateItemsAsync(CultivateProject cultivateProject, List materials)
+ {
+ using (IServiceScope scope = scopeFactory.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+
+ Guid projectId = cultivateProject.InnerId;
+
+ List resultItems = new();
+
+ List entries = await appDbContext.CultivateEntries
+ .AsNoTracking()
+ .Where(e => e.ProjectId == projectId)
+ .ToListAsync()
+ .ConfigureAwait(false);
+
+ foreach (CultivateEntry entry in entries)
+ {
+ Guid entryId = entry.InnerId;
+
+ List 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 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();
+ }
+ }
+
///
public async Task RemoveCultivateEntryAsync(Guid entryId)
{
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs
index f6e12b88..dd912efc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs
@@ -44,6 +44,14 @@ internal interface ICultivationService
/// 项目集合
ObservableCollection GetProjectCollection();
+ ///
+ /// 异步获取统计物品列表
+ ///
+ /// 养成计划
+ /// 元数据
+ /// 统计物品列表
+ Task> GetStatisticsCultivateItemsAsync(CultivateProject cultivateProject, List materials);
+
///
/// 删除养成清单
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
index a4167f77..34e9e1dd 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs
@@ -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();
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs
index f203d0b9..c52a5cc0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchConfiguration.cs
@@ -8,6 +8,11 @@ namespace Snap.Hutao.Service.Game;
///
internal readonly struct LaunchConfiguration
{
+ ///
+ /// 是否为独占全屏
+ ///
+ public readonly bool IsExclusive;
+
///
/// 是否全屏,全屏时无边框设置将被覆盖
///
@@ -41,14 +46,16 @@ internal readonly struct LaunchConfiguration
///
/// 构造一个新的启动配置
///
+ /// 独占全屏
/// 全屏
/// 无边框
/// 宽度
/// 高度
/// 解锁帧率
/// 目标帧率
- 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;
}
-
- ///
- /// 窗口模式字符串
- ///
- public string? WindowMode
- {
- get
- {
- return IsFullScreen ? "exclusive" : IsBorderless ? "borderless" : null;
- }
- }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index 9b3d8d0b..eb65dd03 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -139,7 +139,7 @@
-
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
index d9db0832..89dc62c4 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml
@@ -15,6 +15,10 @@
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
+
+
+
+
@@ -77,12 +81,8 @@
Command="{Binding RemoveArchiveCommand}"
Icon="{shcm:FontIcon Glyph=}"
Label="删除当前存档"/>
-
-
+
@@ -102,6 +102,7 @@
Icon="{shcm:FontIcon Glyph=}"
Label="导出"/>
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml
index c8f788b9..037c1923 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml
@@ -49,6 +49,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -97,6 +139,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -231,117 +306,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
index acbb837a..a33e2da5 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml
@@ -158,6 +158,17 @@
+
+
+
+
+
[Injection(InjectAs.Scoped)]
+[SuppressMessage("", "SA1124")]
internal class AchievementViewModel
: ObservableObject,
ISupportCancellation,
- INavigationRecipient,
- IDisposable,
- IRecipient
+ 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 openUICompletionSource = new();
- private bool disposed;
-
private AdvancedCollectionView? achievements;
private List? achievementGoals;
private Model.Binding.Achievement.AchievementGoal? selectedAchievementGoal;
@@ -94,9 +91,7 @@ internal class AchievementViewModel
RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync);
SearchAchievementCommand = new RelayCommand(SearchAchievement);
SortIncompletedSwitchCommand = new RelayCommand(UpdateAchievementsSort);
- RefreshFinishPercentCommand = new RelayCommand(UpdateAchievementFinishPercent);
-
- messenger.Register(this);
+ SaveAchievementCommand = new RelayCommand(SaveAchievement);
}
///
@@ -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; }
///
- /// 刷新完成百分比命令
+ /// 保存单个成就命令
///
- public ICommand RefreshFinishPercentCommand { get; }
-
- ///
- public void Receive(AchievementArchiveChangedMessage message)
- {
- HandleArchiveChangeAsync(message.OldValue, message.NewValue).SafeForget();
- }
-
- ///
- public void Dispose()
- {
- if (!disposed)
- {
- if (Achievements != null && SelectedArchive != null)
- {
- achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList)!);
- }
-
- disposed = true;
- }
- }
+ public ICommand SaveAchievementCommand { get; }
///
public async Task 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 ShowImportResultDialogAsync(string title, string message)
{
+ MainWindow mainWindow = Ioc.Default.GetRequiredService();
ContentDialog dialog = new()
{
Title = title,
Content = message,
PrimaryButtonText = "确认",
DefaultButton = ContentDialogButton.Primary,
+ XamlRoot = mainWindow.Content.XamlRoot,
};
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- return dialog.InitializeWithWindow(mainWindow).ShowAsync().AsTask();
+ return dialog.ShowAsync().AsTask();
}
private static Task ShowImportFailDialogAsync(string message)
{
+ MainWindow mainWindow = Ioc.Default.GetRequiredService();
ContentDialog dialog = new()
{
Title = "导入失败",
Content = message,
PrimaryButtonText = "确认",
DefaultButton = ContentDialogButton.Primary,
+ XamlRoot = mainWindow.Content.XamlRoot,
};
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- 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)!);
- }
-
- 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 rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
- List 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();
@@ -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)!);
-
await ThreadHelper.SwitchToMainThreadAsync();
IPickerFactory pickerFactory = Ioc.Default.GetRequiredService();
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 rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
+ List 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 counter = AchievementGoals.ToDictionary(x => x.Id, x => new AchievementGoalAggregation(x));
- foreach (Model.Binding.Achievement.Achievement achievement in Achievements.OfType())
+ Dictionary counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalAggregation(x));
+ foreach (Model.Binding.Achievement.Achievement achievement in Achievements.SourceCollection.OfType())
{
- 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;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs
index aae44fe2..0e00aa91 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs
@@ -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;
/// 养成视图模型
///
[Injection(InjectAs.Scoped)]
-internal class CultivationViewModel : ObservableObject, ISupportCancellation, IRecipient
+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? inventoryItems;
private ObservableCollection? cultivateEntries;
+ private List? statisticsItems;
///
/// 构造一个新的养成视图模型
@@ -40,14 +42,12 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
/// 异步命令工厂
/// 元数据服务
/// 日志器
- /// 消息器
public CultivationViewModel(
ICultivationService cultivationService,
IInfoBarService infoBarService,
IAsyncRelayCommandFactory asyncRelayCommandFactory,
IMetadataService metadataService,
- ILogger logger,
- IMessenger messenger)
+ ILogger logger)
{
this.cultivationService = cultivationService;
this.infoBarService = infoBarService;
@@ -59,8 +59,7 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
RemoveProjectCommand = asyncRelayCommandFactory.Create(RemoveProjectAsync);
RemoveEntryCommand = asyncRelayCommandFactory.Create(RemoveEntryAsync);
SaveInventoryItemCommand = new RelayCommand(SaveInventoryItem);
-
- messenger.Register(this);
+ UpdateStatisticsItemsCommand = asyncRelayCommandFactory.Create(UpdateStatisticsItemsAsync);
}
///
@@ -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
///
public ObservableCollection? CultivateEntries { get => cultivateEntries; set => SetProperty(ref cultivateEntries, value); }
+ ///
+ /// 统计列表
+ ///
+ public List? StatisticsItems { get => statisticsItems; set => SetProperty(ref statisticsItems, value); }
+
///
/// 打开界面命令
///
@@ -120,11 +128,10 @@ internal class CultivationViewModel : ObservableObject, ISupportCancellation, IR
///
public ICommand SaveInventoryItemCommand { get; }
- ///
- public void Receive(CultivateProjectChangedMessage message)
- {
- UpdateCultivateEntriesAndInventoryItemsAsync(message.NewValue).SafeForget(logger);
- }
+ ///
+ /// 展示统计物品命令
+ ///
+ 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 materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false);
+ List temp = await cultivationService.GetStatisticsCultivateItemsAsync(SelectedProject, materials).ConfigureAwait(false);
+ await ThreadHelper.SwitchToMainThreadAsync();
+ StatisticsItems = temp;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
index b0c1268d..52e1bf56 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs
@@ -51,6 +51,7 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
private LaunchScheme? selectedScheme;
private ObservableCollection? 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
///
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
+ ///
+ /// 是否为独占全屏
+ ///
+ public bool IsExclusive
+ {
+ get => isExclusive; set
+ {
+ if (SetProperty(ref isExclusive, value))
+ {
+ if (value)
+ {
+ IsFullScreen = true;
+ }
+ }
+ }
+ }
+
///
/// 全屏
///
- 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;
+ }
+ }
+ }
+ }
///
/// 无边框
///
- 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;
+ }
+ }
+ }
+ }
///
/// 宽度
@@ -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);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
index 924a053c..8a19d1a3 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/UserViewModel.cs
@@ -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);
+ }
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/PatchClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/PatchClient.cs
index 40f03622..ed729444 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/PatchClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/PatchClient.cs
@@ -40,10 +40,10 @@ internal class PatchClient
}
///
- /// 更新信息
+ /// 异步获取更新信息
///
- ///
- ///
+ /// 取消令牌
+ /// 更新信息
public Task GetPatchInformationAsync(CancellationToken token = default)
{
return httpClient.TryCatchGetFromJsonAsync(ApiEndpoints.PatcherHutaoStable, options, logger, token);