improve db layer & homecard experience

This commit is contained in:
DismissedLight
2023-09-10 17:38:18 +08:00
committed by Lightczx
parent 5d03f5d0b5
commit 7af57118bc
57 changed files with 860 additions and 465 deletions

View File

@@ -1,55 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// AppTitleBar Workaround
/// https://github.com/microsoft/microsoft-ui-xaml/issues/7756
/// </summary>
internal sealed class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase<ComboBox>
{
private readonly IMessenger messenger;
private readonly EventHandler<object> dropDownOpenedHandler;
private readonly EventHandler<object> dropDownClosedHandler;
/// <summary>
/// AppTitleBar Workaround
/// </summary>
public ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior()
{
messenger = Ioc.Default.GetRequiredService<IMessenger>();
dropDownOpenedHandler = OnDropDownOpened;
dropDownClosedHandler = OnDropDownClosed;
}
/// <inheritdoc/>
protected override bool Initialize()
{
AssociatedObject.DropDownOpened += dropDownOpenedHandler;
AssociatedObject.DropDownClosed += dropDownClosedHandler;
return true;
}
/// <inheritdoc/>
protected override bool Uninitialize()
{
AssociatedObject.DropDownOpened -= dropDownOpenedHandler;
AssociatedObject.DropDownClosed -= dropDownClosedHandler;
return true;
}
private void OnDropDownOpened(object? sender, object e)
{
messenger.Send(Message.FlyoutStateChangedMessage.Open);
}
private void OnDropDownClosed(object? sender, object e)
{
messenger.Send(Message.FlyoutStateChangedMessage.Close);
}
}

View File

@@ -1,22 +1,23 @@
// 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 CommunityToolkit.WinUI.Notifications;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.DailyNote;
using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.ViewModel.Guide;
using System.Diagnostics;
namespace Snap.Hutao.Core.LifeCycle; namespace Snap.Hutao.Core.LifeCycle;
[Injection(InjectAs.Singleton, typeof(ICurrentWindowReference))] [Injection(InjectAs.Singleton, typeof(ICurrentWindowReference))]
internal sealed class CurrentWindowReference : ICurrentWindowReference internal sealed class CurrentWindowReference : ICurrentWindowReference
{ {
public Window Window { get; set; } = default!; private readonly WeakReference<Window> reference = new(default!);
[SuppressMessage("", "SH007")]
public Window Window
{
get
{
reference.TryGetTarget(out Window? window);
return window!;
}
set => reference.SetTarget(value);
}
} }

View File

@@ -1,55 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
namespace Snap.Hutao.Core.Setting;
/// <summary>
/// 功能
/// </summary>
internal sealed class Feature : ObservableObject
{
private readonly string displayName;
private readonly string description;
private readonly string settingKey;
private readonly bool defaultValue;
/// <summary>
/// 构造一个新的功能
/// </summary>
/// <param name="displayName">显示名称</param>
/// <param name="description">描述</param>
/// <param name="settingKey">键</param>
/// <param name="defaultValue">默认值</param>
public Feature(string displayName, string description, string settingKey, bool defaultValue)
{
this.displayName = displayName;
this.description = description;
this.settingKey = settingKey;
this.defaultValue = defaultValue;
}
/// <summary>
/// 显示名称
/// </summary>
public string DisplayName { get => displayName; }
/// <summary>
/// 描述
/// </summary>
public string Description { get => description; }
/// <summary>
/// 值
/// </summary>
public bool Value
{
get => LocalSetting.Get(settingKey, defaultValue);
set
{
LocalSetting.Set(settingKey, value);
OnPropertyChanged();
}
}
}

View File

@@ -1,41 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections;
namespace Snap.Hutao.Core.Setting;
/// <summary>
/// 功能选项
/// </summary>
internal sealed class FeatureOptions : IReadOnlyCollection<Feature>
{
/// <summary>
/// 启用实时便笺无感验证
/// </summary>
public Feature IsDailyNoteSilentVerificationEnabled { get; } = new(
"IsDailyNoteSilentVerificationEnabled", "启用实时便笺无感验证", "IsDailyNoteSilentVerificationEnabled", true);
/// <summary>
/// 元数据检查是否忽略
/// </summary>
public Feature IsMetadataUpdateCheckSuppressed { get; } = new(
"IsMetadataUpdateCheckSuppressed", "禁用元数据更新检查", "IsMetadataUpdateCheckSuppressed", false);
/// <inheritdoc/>
public int Count { get => 2; }
/// <inheritdoc/>
public IEnumerator<Feature> GetEnumerator()
{
// TODO: Use source generator
yield return IsDailyNoteSilentVerificationEnabled;
yield return IsMetadataUpdateCheckSuppressed;
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

View File

@@ -76,4 +76,9 @@ internal static class SettingKeys
public const string CultivationWeapon90LevelTarget = "CultivationWeapon90LevelTarget"; public const string CultivationWeapon90LevelTarget = "CultivationWeapon90LevelTarget";
public const string CultivationWeapon70LevelCurrent = "CultivationWeapon70LevelCurrent"; public const string CultivationWeapon70LevelCurrent = "CultivationWeapon70LevelCurrent";
public const string CultivationWeapon70LevelTarget = "CultivationWeapon70LevelTarget"; public const string CultivationWeapon70LevelTarget = "CultivationWeapon70LevelTarget";
public const string IsHomeCardLaunchGamePresented = "IsHomeCardLaunchGamePresented";
public const string IsHomeCardGachaStatisticsPresented = "IsHomeCardGachaStatisticsPresented";
public const string IsHomeCardAchievementPresented = "IsHomeCardAchievementPresented";
public const string IsHomeCardDailyNotePresented = "IsHomeCardDailyNotePresented";
} }

View File

@@ -17,6 +17,10 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
public void CreateDesktopShoutcutForElevatedLaunch() public void CreateDesktopShoutcutForElevatedLaunch()
{ {
string sourceLogoPath = Path.Combine(runtimeOptions.InstalledLocation, "Assets/Logo.ico");
string targetLogoPath = Path.Combine(runtimeOptions.DataFolder, "ShellLinkLogo.ico");
File.Copy(sourceLogoPath, targetLogoPath);
IShellLinkW shellLink = (IShellLinkW)new ShellLink(); IShellLinkW shellLink = (IShellLinkW)new ShellLink();
shellLink.SetPath("powershell"); shellLink.SetPath("powershell");
shellLink.SetArguments($""" shellLink.SetArguments($"""
@@ -24,8 +28,7 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
"""); """);
shellLink.SetShowCmd(SHOW_WINDOW_CMD.SW_SHOWMINNOACTIVE); shellLink.SetShowCmd(SHOW_WINDOW_CMD.SW_SHOWMINNOACTIVE);
string iconPath = Path.Combine(runtimeOptions.InstalledLocation, "Snap.Hutao.exe"); shellLink.SetIconLocation(targetLogoPath, 0);
shellLink.SetIconLocation(iconPath, 0);
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string target = Path.Combine(desktop, $"{SH.AppNameAndVersion.Format(runtimeOptions.Version)}.lnk"); string target = Path.Combine(desktop, $"{SH.AppNameAndVersion.Format(runtimeOptions.Version)}.lnk");

View File

@@ -22,9 +22,6 @@ internal sealed class CalculableWeapon
IMappingFrom<CalculableWeapon, Weapon>, IMappingFrom<CalculableWeapon, Weapon>,
IMappingFrom<CalculableWeapon, WeaponView> IMappingFrom<CalculableWeapon, WeaponView>
{ {
private uint levelCurrent;
private uint levelTarget;
/// <summary> /// <summary>
/// 构造一个新的可计算武器 /// 构造一个新的可计算武器
/// </summary> /// </summary>

View File

@@ -13,32 +13,8 @@ internal sealed class UIIFItem
/// 物品Id /// 物品Id
/// </summary> /// </summary>
[JsonPropertyName("itemId")] [JsonPropertyName("itemId")]
public int ItemId { get; set; } public uint ItemId { get; set; }
/// <summary> [JsonPropertyName("material")]
/// 物品Id public UIIFMaterial Material { get; set; } = default!;
/// </summary>
[JsonPropertyName("count")]
public int Count { get; set; }
/// <summary>
/// 等级
/// Reliquary/Weapon
/// </summary>
[JsonPropertyName("level")]
public int? Level { get; set; }
/// <summary>
/// 副属性列表
/// Reliquary
/// </summary>
[JsonPropertyName("appendPropIdList")]
public List<int>? AppendPropIdList { get; set; } = default!;
/// <summary>
/// 精炼等级 0-4
/// Weapon
/// </summary>
[JsonPropertyName("promoteLevel")]
public int? PromoteLevel { get; set; }
} }

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.InterChange.Inventory;
internal sealed class UIIFMaterial
{
public uint Count { get; set; }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@@ -5245,7 +5245,7 @@ namespace Snap.Hutao.Resource.Localization {
} }
/// <summary> /// <summary>
/// 查找类似 在桌面上创建默认以管理员方式启动的快捷方式,更新后需要重新创建 的本地化字符串。 /// 查找类似 在桌面上创建默认以管理员方式启动的快捷方式 的本地化字符串。
/// </summary> /// </summary>
internal static string ViewPageSettingCreateDesktopShortcutDescription { internal static string ViewPageSettingCreateDesktopShortcutDescription {
get { get {
@@ -5469,6 +5469,87 @@ namespace Snap.Hutao.Resource.Localization {
} }
} }
/// <summary>
/// 查找类似 管理主页仪表板中的卡片 的本地化字符串。
/// </summary>
internal static string ViewpageSettingHomeCardDescription {
get {
return ResourceManager.GetString("ViewpageSettingHomeCardDescription", resourceCulture);
}
}
/// <summary>
/// 查找类似 主页卡片 的本地化字符串。
/// </summary>
internal static string ViewpageSettingHomeCardHeader {
get {
return ResourceManager.GetString("ViewpageSettingHomeCardHeader", resourceCulture);
}
}
/// <summary>
/// 查找类似 成就管理 的本地化字符串。
/// </summary>
internal static string ViewpageSettingHomeCardItemAchievementHeader {
get {
return ResourceManager.GetString("ViewpageSettingHomeCardItemAchievementHeader", resourceCulture);
}
}
/// <summary>
/// 查找类似 实时便笺 的本地化字符串。
/// </summary>
internal static string ViewpageSettingHomeCardItemDailyNoteHeader {
get {
return ResourceManager.GetString("ViewpageSettingHomeCardItemDailyNoteHeader", resourceCulture);
}
}
/// <summary>
/// 查找类似 祈愿记录 的本地化字符串。
/// </summary>
internal static string ViewpageSettingHomeCardItemgachaStatisticsHeader {
get {
return ResourceManager.GetString("ViewpageSettingHomeCardItemgachaStatisticsHeader", resourceCulture);
}
}
/// <summary>
/// 查找类似 启动游戏 的本地化字符串。
/// </summary>
internal static string ViewpageSettingHomeCardItemLaunchGameHeader {
get {
return ResourceManager.GetString("ViewpageSettingHomeCardItemLaunchGameHeader", resourceCulture);
}
}
/// <summary>
/// 查找类似 隐藏 的本地化字符串。
/// </summary>
internal static string ViewPageSettingHomeCardOff {
get {
return ResourceManager.GetString("ViewPageSettingHomeCardOff", resourceCulture);
}
}
/// <summary>
/// 查找类似 显示 的本地化字符串。
/// </summary>
internal static string ViewPageSettingHomeCardOn {
get {
return ResourceManager.GetString("ViewPageSettingHomeCardOn", resourceCulture);
}
}
/// <summary>
/// 查找类似 主页 的本地化字符串。
/// </summary>
internal static string ViewpageSettingHomeHeader {
get {
return ResourceManager.GetString("ViewpageSettingHomeHeader", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 胡桃账号 的本地化字符串。 /// 查找类似 胡桃账号 的本地化字符串。
/// </summary> /// </summary>
@@ -5577,6 +5658,15 @@ namespace Snap.Hutao.Resource.Localization {
} }
} }
/// <summary>
/// 查找类似 胡桃使用 PowerShell 更改注册表中的信息以修改游戏内账号 的本地化字符串。
/// </summary>
internal static string ViewPageSettingSetPowerShellDescription {
get {
return ResourceManager.GetString("ViewPageSettingSetPowerShellDescription", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 PowerShell 路径 的本地化字符串。 /// 查找类似 PowerShell 路径 的本地化字符串。
/// </summary> /// </summary>
@@ -5586,6 +5676,15 @@ namespace Snap.Hutao.Resource.Localization {
} }
} }
/// <summary>
/// 查找类似 Shell 体验 的本地化字符串。
/// </summary>
internal static string ViewPageSettingShellExperienceHeader {
get {
return ResourceManager.GetString("ViewPageSettingShellExperienceHeader", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 赞助我们 的本地化字符串。 /// 查找类似 赞助我们 的本地化字符串。
/// </summary> /// </summary>

View File

@@ -1902,7 +1902,7 @@
<value>创建</value> <value>创建</value>
</data> </data>
<data name="ViewPageSettingCreateDesktopShortcutDescription" xml:space="preserve"> <data name="ViewPageSettingCreateDesktopShortcutDescription" xml:space="preserve">
<value>在桌面上创建默认以管理员方式启动的快捷方式,更新后需要重新创建</value> <value>在桌面上创建默认以管理员方式启动的快捷方式</value>
</data> </data>
<data name="ViewPageSettingCreateDesktopShortcutHeader" xml:space="preserve"> <data name="ViewPageSettingCreateDesktopShortcutHeader" xml:space="preserve">
<value>创建快捷方式</value> <value>创建快捷方式</value>
@@ -1976,6 +1976,33 @@
<data name="ViewPageSettingGeetestVerificationHeader" xml:space="preserve"> <data name="ViewPageSettingGeetestVerificationHeader" xml:space="preserve">
<value>无感验证</value> <value>无感验证</value>
</data> </data>
<data name="ViewpageSettingHomeCardDescription" xml:space="preserve">
<value>管理主页仪表板中的卡片</value>
</data>
<data name="ViewpageSettingHomeCardHeader" xml:space="preserve">
<value>主页卡片</value>
</data>
<data name="ViewpageSettingHomeCardItemAchievementHeader" xml:space="preserve">
<value>成就管理</value>
</data>
<data name="ViewpageSettingHomeCardItemDailyNoteHeader" xml:space="preserve">
<value>实时便笺</value>
</data>
<data name="ViewpageSettingHomeCardItemgachaStatisticsHeader" xml:space="preserve">
<value>祈愿记录</value>
</data>
<data name="ViewpageSettingHomeCardItemLaunchGameHeader" xml:space="preserve">
<value>启动游戏</value>
</data>
<data name="ViewPageSettingHomeCardOff" xml:space="preserve">
<value>隐藏</value>
</data>
<data name="ViewPageSettingHomeCardOn" xml:space="preserve">
<value>显示</value>
</data>
<data name="ViewpageSettingHomeHeader" xml:space="preserve">
<value>主页</value>
</data>
<data name="ViewPageSettingHutaoPassportHeader" xml:space="preserve"> <data name="ViewPageSettingHutaoPassportHeader" xml:space="preserve">
<value>胡桃账号</value> <value>胡桃账号</value>
</data> </data>
@@ -2012,9 +2039,15 @@
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve"> <data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
<value>设置游戏路径时请选择游戏本体YuanShen.exe 或 GenshinImpact.exe而不是启动器launcher.exe</value> <value>设置游戏路径时请选择游戏本体YuanShen.exe 或 GenshinImpact.exe而不是启动器launcher.exe</value>
</data> </data>
<data name="ViewPageSettingSetPowerShellDescription" xml:space="preserve">
<value>胡桃使用 PowerShell 更改注册表中的信息以修改游戏内账号</value>
</data>
<data name="ViewPageSettingSetPowerShellPathHeader" xml:space="preserve"> <data name="ViewPageSettingSetPowerShellPathHeader" xml:space="preserve">
<value>PowerShell 路径</value> <value>PowerShell 路径</value>
</data> </data>
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
<value>Shell 体验</value>
</data>
<data name="ViewPageSettingSponsorNavigate" xml:space="preserve"> <data name="ViewPageSettingSponsorNavigate" xml:space="preserve">
<value>赞助我们</value> <value>赞助我们</value>
</data> </data>

View File

@@ -89,6 +89,21 @@ internal sealed partial class AchievementDbService : IAchievementDbService
} }
} }
public async ValueTask OverwriteAchievementAsync(EntityAchievement achievement)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// Delete exists one.
await appDbContext.Achievements.ExecuteDeleteWhereAsync(e => e.InnerId == achievement.InnerId).ConfigureAwait(false);
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
{
await appDbContext.Achievements.AddAndSaveAsync(achievement).ConfigureAwait(false);
}
}
}
public ObservableCollection<AchievementArchive> GetAchievementArchiveCollection() public ObservableCollection<AchievementArchive> GetAchievementArchiveCollection()
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -98,7 +113,7 @@ internal sealed partial class AchievementDbService : IAchievementDbService
} }
} }
public async ValueTask DeleteAchievementArchiveAsync(AchievementArchive archive) public async ValueTask RemoveAchievementArchiveAsync(AchievementArchive archive)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -121,6 +136,19 @@ internal sealed partial class AchievementDbService : IAchievementDbService
} }
} }
public async ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.Achievements
.AsNoTracking()
.Where(i => i.ArchiveId == archiveId)
.ToListAsync()
.ConfigureAwait(false);
}
}
public List<AchievementArchive> GetAchievementArchiveList() public List<AchievementArchive> GetAchievementArchiveList()
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -129,4 +157,13 @@ internal sealed partial class AchievementDbService : IAchievementDbService
return appDbContext.AchievementArchives.AsNoTracking().ToList(); return appDbContext.AchievementArchives.AsNoTracking().ToList();
} }
} }
public async ValueTask<List<AchievementArchive>> GetAchievementArchiveListAsync()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.AchievementArchives.AsNoTracking().ToListAsync().ConfigureAwait(false);
}
}
} }

View File

@@ -74,6 +74,6 @@ internal sealed partial class AchievementService
// Sync database // Sync database
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await achievementDbService.DeleteAchievementArchiveAsync(archive).ConfigureAwait(false); await achievementDbService.RemoveAchievementArchiveAsync(archive).ConfigureAwait(false);
} }
} }

View File

@@ -50,9 +50,10 @@ internal sealed partial class AchievementService
public async ValueTask<UIAF> ExportToUIAFAsync(AchievementArchive archive) public async ValueTask<UIAF> ExportToUIAFAsync(AchievementArchive archive)
{ {
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
List<UIAFItem> list = achievementDbService List<EntityAchievement> entities = await achievementDbService
.GetAchievementListByArchiveId(archive.InnerId) .GetAchievementListByArchiveIdAsync(archive.InnerId)
.SelectList(UIAFItem.From); .ConfigureAwait(false);
List<UIAFItem> list = entities.SelectList(UIAFItem.From);
return new() return new()
{ {

View File

@@ -22,7 +22,7 @@ internal sealed partial class AchievementStatisticsService : IAchievementStatist
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
List<AchievementStatistics> results = new(); List<AchievementStatistics> results = new();
foreach (AchievementArchive archive in achievementDbService.GetAchievementArchiveList()) foreach (AchievementArchive archive in await achievementDbService.GetAchievementArchiveListAsync().ConfigureAwait(false))
{ {
int finishedCount = await achievementDbService int finishedCount = await achievementDbService
.GetFinishedAchievementCountByArchiveIdAsync(archive.InnerId) .GetFinishedAchievementCountByArchiveIdAsync(archive.InnerId)

View File

@@ -9,14 +9,18 @@ namespace Snap.Hutao.Service.Achievement;
internal interface IAchievementDbService internal interface IAchievementDbService
{ {
ValueTask DeleteAchievementArchiveAsync(Model.Entity.AchievementArchive archive); ValueTask RemoveAchievementArchiveAsync(Model.Entity.AchievementArchive archive);
ObservableCollection<Model.Entity.AchievementArchive> GetAchievementArchiveCollection(); ObservableCollection<Model.Entity.AchievementArchive> GetAchievementArchiveCollection();
List<Model.Entity.AchievementArchive> GetAchievementArchiveList(); List<Model.Entity.AchievementArchive> GetAchievementArchiveList();
ValueTask<List<Model.Entity.AchievementArchive>> GetAchievementArchiveListAsync();
List<EntityAchievement> GetAchievementListByArchiveId(Guid archiveId); List<EntityAchievement> GetAchievementListByArchiveId(Guid archiveId);
ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId);
Dictionary<AchievementId, EntityAchievement> GetAchievementMapByArchiveId(Guid archiveId); Dictionary<AchievementId, EntityAchievement> GetAchievementMapByArchiveId(Guid archiveId);
ValueTask<int> GetFinishedAchievementCountByArchiveIdAsync(Guid archiveId); ValueTask<int> GetFinishedAchievementCountByArchiveIdAsync(Guid archiveId);
@@ -24,4 +28,6 @@ internal interface IAchievementDbService
ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take); ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take);
void OverwriteAchievement(EntityAchievement achievement); void OverwriteAchievement(EntityAchievement achievement);
ValueTask OverwriteAchievementAsync(EntityAchievement achievement);
} }

View File

@@ -77,7 +77,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
string uid = userAndUid.Uid.Value; string uid = userAndUid.Uid.Value;
List<EntityAvatarInfo> dbInfos = avatarInfoDbService.GetAvatarInfoListByUid(uid); List<EntityAvatarInfo> dbInfos = await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false);
EnsureItemsAvatarIdDistinct(ref dbInfos, uid); EnsureItemsAvatarIdDistinct(ref dbInfos, uid);
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -121,7 +121,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
} }
} }
return avatarInfoDbService.GetAvatarInfoListByUid(uid); return await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@@ -134,7 +134,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
string uid = userAndUid.Uid.Value; string uid = userAndUid.Uid.Value;
List<EntityAvatarInfo> dbInfos = avatarInfoDbService.GetAvatarInfoListByUid(uid); List<EntityAvatarInfo> dbInfos = await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false);
EnsureItemsAvatarIdDistinct(ref dbInfos, uid); EnsureItemsAvatarIdDistinct(ref dbInfos, uid);
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -173,7 +173,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
} }
} }
return avatarInfoDbService.GetAvatarInfoListByUid(uid); return await avatarInfoDbService.GetAvatarInfoListByUidAsync(uid).ConfigureAwait(false);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -243,7 +243,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
// This means that there are duplicate items. // This means that there are duplicate items.
if (distinctCount < dbInfos.Count) if (distinctCount < dbInfos.Count)
{ {
avatarInfoDbService.DeleteAvatarInfoRangeByUid(uid); avatarInfoDbService.RemoveAvatarInfoRangeByUid(uid);
dbInfos = new(); dbInfos = new();
} }
} }

View File

@@ -24,7 +24,20 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
} }
} }
public void DeleteAvatarInfoRangeByUid(string uid) public async ValueTask<List<EntityAvatarInfo>> GetAvatarInfoListByUidAsync(string uid)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.AvatarInfos
.AsNoTracking()
.Where(i => i.Uid == uid)
.ToListAsync()
.ConfigureAwait(false);
}
}
public void RemoveAvatarInfoRangeByUid(string uid)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -32,4 +45,13 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
appDbContext.AvatarInfos.ExecuteDeleteWhere(i => i.Uid == uid); appDbContext.AvatarInfos.ExecuteDeleteWhere(i => i.Uid == uid);
} }
} }
public async ValueTask RemoveAvatarInfoRangeByUidAsync(string uid)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.AvatarInfos.ExecuteDeleteWhereAsync(i => i.Uid == uid).ConfigureAwait(false);
}
}
} }

View File

@@ -78,7 +78,7 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
default: default:
{ {
List<EntityAvatarInfo> list = avatarInfoDbService.GetAvatarInfoListByUid(userAndUid.Uid.Value); List<EntityAvatarInfo> list = await avatarInfoDbService.GetAvatarInfoListByUidAsync(userAndUid.Uid.Value).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false); Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
return new(RefreshResult.Ok, summary.Avatars.Count == 0 ? null : summary); return new(RefreshResult.Ok, summary.Avatars.Count == 0 ? null : summary);
@@ -97,7 +97,7 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
?? await enkaClient.GetDataAsync(uid, token).ConfigureAwait(false); ?? await enkaClient.GetDataAsync(uid, token).ConfigureAwait(false);
} }
private async ValueTask<Summary> GetSummaryCoreAsync(IEnumerable<Model.Entity.AvatarInfo> avatarInfos, CancellationToken token) private async ValueTask<Summary> GetSummaryCoreAsync(IEnumerable<EntityAvatarInfo> avatarInfos, CancellationToken token)
{ {
using (ValueStopwatch.MeasureExecution(logger)) using (ValueStopwatch.MeasureExecution(logger))
{ {

View File

@@ -7,7 +7,11 @@ namespace Snap.Hutao.Service.AvatarInfo;
internal interface IAvatarInfoDbService internal interface IAvatarInfoDbService
{ {
void DeleteAvatarInfoRangeByUid(string uid); void RemoveAvatarInfoRangeByUid(string uid);
List<EntityAvatarInfo> GetAvatarInfoListByUid(string uid); List<EntityAvatarInfo> GetAvatarInfoListByUid(string uid);
ValueTask<List<EntityAvatarInfo>> GetAvatarInfoListByUidAsync(string uid);
ValueTask RemoveAvatarInfoRangeByUidAsync(string uid);
} }

View File

@@ -66,7 +66,7 @@ internal sealed partial class CultivationDbService : ICultivationDbService
} }
} }
public async ValueTask DeleteCultivateEntryByIdAsync(Guid entryId) public async ValueTask RemoveCultivateEntryByIdAsync(Guid entryId)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -77,15 +77,6 @@ internal sealed partial class CultivationDbService : ICultivationDbService
} }
} }
public void UpdateInventoryItem(InventoryItem item)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.InventoryItems.UpdateAndSave(item);
}
}
public void UpdateCultivateItem(CultivateItem item) public void UpdateCultivateItem(CultivateItem item)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -95,6 +86,15 @@ internal sealed partial class CultivationDbService : ICultivationDbService
} }
} }
public async ValueTask UpdateCultivateItemAsync(CultivateItem item)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateItems.UpdateAndSaveAsync(item).ConfigureAwait(false);
}
}
public async ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId) public async ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -115,7 +115,7 @@ internal sealed partial class CultivationDbService : ICultivationDbService
} }
} }
public async ValueTask DeleteCultivateItemRangeByEntryIdAsync(Guid entryId) public async ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -144,7 +144,7 @@ internal sealed partial class CultivationDbService : ICultivationDbService
} }
} }
public async ValueTask DeleteCultivateProjectByIdAsync(Guid projectId) public async ValueTask RemoveCultivateProjectByIdAsync(Guid projectId)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {

View File

@@ -75,6 +75,6 @@ internal sealed partial class CultivationService
// Sync database // Sync database
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.DeleteCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false); await cultivationDbService.RemoveCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false);
} }
} }

View File

@@ -8,6 +8,7 @@ using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Model.Metadata.Item; using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Inventroy;
using Snap.Hutao.ViewModel.Cultivation; using Snap.Hutao.ViewModel.Cultivation;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -23,6 +24,7 @@ internal sealed partial class CultivationService : ICultivationService
{ {
private readonly ScopedDbCurrent<CultivateProject, Message.CultivateProjectChangedMessage> dbCurrent; private readonly ScopedDbCurrent<CultivateProject, Message.CultivateProjectChangedMessage> dbCurrent;
private readonly ICultivationDbService cultivationDbService; private readonly ICultivationDbService cultivationDbService;
private readonly IInventoryDbService inventoryDbService;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
@@ -140,13 +142,13 @@ internal sealed partial class CultivationService : ICultivationService
public async ValueTask RemoveCultivateEntryAsync(Guid entryId) public async ValueTask RemoveCultivateEntryAsync(Guid entryId)
{ {
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.DeleteCultivateEntryByIdAsync(entryId).ConfigureAwait(false); await cultivationDbService.RemoveCultivateEntryByIdAsync(entryId).ConfigureAwait(false);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SaveInventoryItem(InventoryItemView item) public void SaveInventoryItem(InventoryItemView item)
{ {
cultivationDbService.UpdateInventoryItem(item.Entity); inventoryDbService.UpdateInventoryItem(item.Entity);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -181,7 +183,7 @@ internal sealed partial class CultivationService : ICultivationService
} }
Guid entryId = entry.InnerId; Guid entryId = entry.InnerId;
await cultivationDbService.DeleteCultivateItemRangeByEntryIdAsync(entryId).ConfigureAwait(false); await cultivationDbService.RemoveCultivateItemRangeByEntryIdAsync(entryId).ConfigureAwait(false);
IEnumerable<CultivateItem> toAdd = items.Select(item => CultivateItem.From(entryId, item)); IEnumerable<CultivateItem> toAdd = items.Select(item => CultivateItem.From(entryId, item));
await cultivationDbService.AddCultivateItemRangeAsync(toAdd).ConfigureAwait(false); await cultivationDbService.AddCultivateItemRangeAsync(toAdd).ConfigureAwait(false);

View File

@@ -10,11 +10,11 @@ internal interface ICultivationDbService
{ {
ValueTask AddCultivateProjectAsync(CultivateProject project); ValueTask AddCultivateProjectAsync(CultivateProject project);
ValueTask DeleteCultivateEntryByIdAsync(Guid entryId); ValueTask RemoveCultivateEntryByIdAsync(Guid entryId);
ValueTask DeleteCultivateItemRangeByEntryIdAsync(Guid entryId); ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId);
ValueTask DeleteCultivateProjectByIdAsync(Guid projectId); ValueTask RemoveCultivateProjectByIdAsync(Guid projectId);
ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId); ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId);
@@ -34,5 +34,5 @@ internal interface ICultivationDbService
void UpdateCultivateItem(CultivateItem item); void UpdateCultivateItem(CultivateItem item);
void UpdateInventoryItem(InventoryItem item); ValueTask UpdateCultivateItemAsync(CultivateItem item);
} }

View File

@@ -23,6 +23,15 @@ internal sealed partial class DailyNoteDbService : IDailyNoteDbService
} }
} }
public async ValueTask<bool> ContainsUidAsync(string uid)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.DailyNotes.AsNoTracking().AnyAsync(n => n.Uid == uid).ConfigureAwait(false);
}
}
public async ValueTask AddDailyNoteEntryAsync(DailyNoteEntry entry) public async ValueTask AddDailyNoteEntryAsync(DailyNoteEntry entry)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -50,15 +59,6 @@ internal sealed partial class DailyNoteDbService : IDailyNoteDbService
} }
} }
public List<DailyNoteEntry> GetDailyNoteEntryList()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.DailyNotes.AsNoTracking().ToList();
}
}
public List<DailyNoteEntry> GetDailyNoteEntryIncludeUserList() public List<DailyNoteEntry> GetDailyNoteEntryIncludeUserList()
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -67,4 +67,13 @@ internal sealed partial class DailyNoteDbService : IDailyNoteDbService
return appDbContext.DailyNotes.AsNoTracking().Include(n => n.User).ToList(); return appDbContext.DailyNotes.AsNoTracking().Include(n => n.User).ToList();
} }
} }
public async ValueTask<List<DailyNoteEntry>> GetDailyNoteEntryIncludeUserListAsync()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.DailyNotes.AsNoTracking().Include(n => n.User).ToListAsync().ConfigureAwait(false);
}
}
} }

View File

@@ -42,7 +42,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
{ {
string roleUid = userAndUid.Uid.Value; string roleUid = userAndUid.Uid.Value;
if (!dailyNoteDbService.ContainsUid(roleUid)) if (!await dailyNoteDbService.ContainsUidAsync(roleUid).ConfigureAwait(false))
{ {
DailyNoteEntry newEntry = DailyNoteEntry.From(userAndUid); DailyNoteEntry newEntry = DailyNoteEntry.From(userAndUid);
@@ -75,7 +75,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
await userService.GetRoleCollectionAsync().ConfigureAwait(false); await userService.GetRoleCollectionAsync().ConfigureAwait(false);
await RefreshDailyNotesCoreAsync(forceRefresh).ConfigureAwait(false); await RefreshDailyNotesCoreAsync(forceRefresh).ConfigureAwait(false);
List<DailyNoteEntry> entryList = dailyNoteDbService.GetDailyNoteEntryIncludeUserList(); List<DailyNoteEntry> entryList = await dailyNoteDbService.GetDailyNoteEntryIncludeUserListAsync().ConfigureAwait(false);
entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); }); entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); });
entries = new(entryList); entries = new(entryList);
} }
@@ -108,7 +108,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
private async ValueTask RefreshDailyNotesCoreAsync(bool forceRefresh) private async ValueTask RefreshDailyNotesCoreAsync(bool forceRefresh)
{ {
foreach (DailyNoteEntry entry in dailyNoteDbService.GetDailyNoteEntryIncludeUserList()) foreach (DailyNoteEntry entry in await dailyNoteDbService.GetDailyNoteEntryIncludeUserListAsync().ConfigureAwait(false))
{ {
if (!forceRefresh && entry.DailyNote is not null) if (!forceRefresh && entry.DailyNote is not null)
{ {

View File

@@ -11,11 +11,13 @@ internal interface IDailyNoteDbService
bool ContainsUid(string uid); bool ContainsUid(string uid);
ValueTask<bool> ContainsUidAsync(string uid);
ValueTask DeleteDailyNoteEntryByIdAsync(Guid entryId); ValueTask DeleteDailyNoteEntryByIdAsync(Guid entryId);
List<DailyNoteEntry> GetDailyNoteEntryIncludeUserList(); List<DailyNoteEntry> GetDailyNoteEntryIncludeUserList();
List<DailyNoteEntry> GetDailyNoteEntryList(); ValueTask<List<DailyNoteEntry>> GetDailyNoteEntryIncludeUserListAsync();
ValueTask UpdateDailyNoteEntryAsync(DailyNoteEntry entry); ValueTask UpdateDailyNoteEntryAsync(DailyNoteEntry entry);
} }

View File

@@ -49,10 +49,10 @@ internal readonly struct GachaItemSaveContext
// 全量刷新 // 全量刷新
if (!IsLazy) if (!IsLazy)
{ {
GachaLogDbService.DeleteNewerGachaItemsByArchiveIdQueryTypeAndEndId(archive.InnerId, QueryType, EndId); GachaLogDbService.RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(archive.InnerId, QueryType, EndId);
} }
GachaLogDbService.AddGachaItems(ItemsToAdd); GachaLogDbService.AddGachaItemRange(ItemsToAdd);
} }
} }
} }

View File

@@ -48,7 +48,21 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
} }
} }
public async ValueTask DeleteGachaArchiveByIdAsync(Guid archiveId) public async ValueTask<List<GachaItem>> GetGachaItemListByArchiveIdAsync(Guid archiveId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.GachaItems
.AsNoTracking()
.Where(i => i.ArchiveId == archiveId)
.OrderBy(i => i.Id)
.ToListAsync()
.ConfigureAwait(false);
}
}
public async ValueTask RemoveGachaArchiveByIdAsync(Guid archiveId)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -118,6 +132,36 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? 0L; return item?.Id ?? 0L;
} }
public async ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType)
{
GachaItem? item = null;
try
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// TODO: replace with MaxBy
// https://github.com/dotnet/efcore/issues/25566
// .MaxBy(i => i.Id);
item = await appDbContext.GachaItems
.AsNoTracking()
.Where(i => i.ArchiveId == archiveId)
.Where(i => i.QueryType == queryType)
.OrderByDescending(i => i.Id)
.FirstOrDefaultAsync()
.ConfigureAwait(false);
}
}
catch (SqliteException ex)
{
ThrowHelper.UserdataCorrupted(SH.ServiceGachaLogEndIdUserdataCorruptedMessage, ex);
}
return item?.Id ?? 0L;
}
public long GetOldestGachaItemIdByArchiveId(Guid archiveId) public long GetOldestGachaItemIdByArchiveId(Guid archiveId)
{ {
GachaItem? item = null; GachaItem? item = null;
@@ -139,6 +183,28 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? long.MaxValue; return item?.Id ?? long.MaxValue;
} }
public async ValueTask<long> GetOldestGachaItemIdByArchiveIdAsync(Guid archiveId)
{
GachaItem? item = null;
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// TODO: replace with MaxBy
// https://github.com/dotnet/efcore/issues/25566
// .MaxBy(i => i.Id);
item = await appDbContext.GachaItems
.AsNoTracking()
.Where(i => i.ArchiveId == archiveId)
.OrderBy(i => i.Id)
.FirstOrDefaultAsync()
.ConfigureAwait(false);
}
return item?.Id ?? long.MaxValue;
}
public long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType) public long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType)
{ {
GachaItem? item = null; GachaItem? item = null;
@@ -182,15 +248,6 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? long.MaxValue; return item?.Id ?? long.MaxValue;
} }
public async ValueTask AddGachaArchiveAsync(GachaArchive archive)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GachaArchives.AddAndSaveAsync(archive).ConfigureAwait(false);
}
}
public void AddGachaArchive(GachaArchive archive) public void AddGachaArchive(GachaArchive archive)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -200,6 +257,15 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
} }
} }
public async ValueTask AddGachaArchiveAsync(GachaArchive archive)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GachaArchives.AddAndSaveAsync(archive).ConfigureAwait(false);
}
}
public List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaConfigType queryType, long endId) public List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaConfigType queryType, long endId)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -225,6 +291,32 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
} }
} }
public async ValueTask<List<Web.Hutao.GachaLog.GachaItem>> GetHutaoGachaItemListAsync(Guid archiveId, GachaConfigType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.GachaItems
.AsNoTracking()
.Where(i => i.ArchiveId == archiveId)
.Where(i => i.QueryType == queryType)
.OrderByDescending(i => i.Id)
.Where(i => i.Id > endId)
// Keep this to make SQL generates correctly
.Select(i => new Web.Hutao.GachaLog.GachaItem()
{
GachaType = i.GachaType,
QueryType = i.QueryType,
ItemId = i.ItemId,
Time = i.Time,
Id = i.Id,
})
.ToListAsync()
.ConfigureAwait(false);
}
}
public async ValueTask<GachaArchive?> GetGachaArchiveByIdAsync(Guid archiveId, CancellationToken token) public async ValueTask<GachaArchive?> GetGachaArchiveByIdAsync(Guid archiveId, CancellationToken token)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -258,7 +350,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
} }
} }
public void AddGachaItems(List<GachaItem> items) public void AddGachaItemRange(List<GachaItem> items)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -267,7 +359,16 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
} }
} }
public void DeleteNewerGachaItemsByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaConfigType queryType, long endId) public async ValueTask AddGachaItemRangeAsync(List<GachaItem> items)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GachaItems.AddRangeAndSaveAsync(items).ConfigureAwait(false);
}
}
public void RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaConfigType queryType, long endId)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {
@@ -279,4 +380,18 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
.ExecuteDelete(); .ExecuteDelete();
} }
} }
public async ValueTask RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndIdAsync(Guid archiveId, GachaConfigType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GachaItems
.AsNoTracking()
.Where(i => i.ArchiveId == archiveId && i.QueryType == queryType)
.Where(i => i.Id >= endId)
.ExecuteDeleteAsync()
.ConfigureAwait(false);
}
}
} }

View File

@@ -41,7 +41,9 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
List<Web.Hutao.GachaLog.GachaItem> items = new(); List<Web.Hutao.GachaLog.GachaItem> items = new();
foreach ((GachaConfigType type, long endId) in endIds) foreach ((GachaConfigType type, long endId) in endIds)
{ {
List<Web.Hutao.GachaLog.GachaItem> part = gachaLogDbService.GetHutaoGachaItemList(gachaArchive.InnerId, type, endId); List<Web.Hutao.GachaLog.GachaItem> part = await gachaLogDbService
.GetHutaoGachaItemListAsync(gachaArchive.InnerId, type, endId)
.ConfigureAwait(false);
items.AddRange(part); items.AddRange(part);
} }

View File

@@ -88,7 +88,7 @@ internal sealed partial class GachaLogService : IGachaLogService
// Return statistics // Return statistics
using (ValueStopwatch.MeasureExecution(logger)) using (ValueStopwatch.MeasureExecution(logger))
{ {
List<GachaItem> items = gachaLogDbService.GetGachaItemListByArchiveId(archive.InnerId); List<GachaItem> items = await gachaLogDbService.GetGachaItemListByArchiveIdAsync(archive.InnerId).ConfigureAwait(false);
return await gachaStatisticsFactory.CreateAsync(items, context).ConfigureAwait(false); return await gachaStatisticsFactory.CreateAsync(items, context).ConfigureAwait(false);
} }
} }
@@ -102,7 +102,7 @@ internal sealed partial class GachaLogService : IGachaLogService
List<GachaStatisticsSlim> statistics = new(); List<GachaStatisticsSlim> statistics = new();
foreach (GachaArchive archive in ArchiveCollection) foreach (GachaArchive archive in ArchiveCollection)
{ {
List<GachaItem> items = gachaLogDbService.GetGachaItemListByArchiveId(archive.InnerId); List<GachaItem> items = await gachaLogDbService.GetGachaItemListByArchiveIdAsync(archive.InnerId).ConfigureAwait(false);
GachaStatisticsSlim slim = await gachaStatisticsSlimFactory.CreateAsync(context, items, archive.Uid).ConfigureAwait(false); GachaStatisticsSlim slim = await gachaStatisticsSlimFactory.CreateAsync(context, items, archive.Uid).ConfigureAwait(false);
statistics.Add(slim); statistics.Add(slim);
} }
@@ -150,7 +150,7 @@ internal sealed partial class GachaLogService : IGachaLogService
// Sync database // Sync database
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await gachaLogDbService.DeleteGachaArchiveByIdAsync(archive.InnerId).ConfigureAwait(false); await gachaLogDbService.RemoveGachaArchiveByIdAsync(archive.InnerId).ConfigureAwait(false);
// Sync cache // Sync cache
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();

View File

@@ -13,13 +13,13 @@ internal interface IGachaLogDbService
ValueTask AddGachaArchiveAsync(GachaArchive archive); ValueTask AddGachaArchiveAsync(GachaArchive archive);
void AddGachaItems(List<GachaItem> items); void AddGachaItemRange(List<GachaItem> items);
ValueTask AddGachaItemsAsync(List<GachaItem> items); ValueTask AddGachaItemsAsync(List<GachaItem> items);
ValueTask DeleteGachaArchiveByIdAsync(Guid archiveId); ValueTask RemoveGachaArchiveByIdAsync(Guid archiveId);
void DeleteNewerGachaItemsByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaConfigType queryType, long endId); void RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaConfigType queryType, long endId);
ValueTask<GachaArchive?> GetGachaArchiveByIdAsync(Guid archiveId, CancellationToken token); ValueTask<GachaArchive?> GetGachaArchiveByIdAsync(Guid archiveId, CancellationToken token);
@@ -29,6 +29,8 @@ internal interface IGachaLogDbService
List<GachaItem> GetGachaItemListByArchiveId(Guid archiveId); List<GachaItem> GetGachaItemListByArchiveId(Guid archiveId);
ValueTask<List<GachaItem>> GetGachaItemListByArchiveIdAsync(Guid archiveId);
List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaConfigType queryType, long endId); List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaConfigType queryType, long endId);
long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType); long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType);
@@ -40,4 +42,14 @@ internal interface IGachaLogDbService
long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType); long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType);
ValueTask<long> GetOldestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token); ValueTask<long> GetOldestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token);
ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType);
ValueTask<long> GetOldestGachaItemIdByArchiveIdAsync(Guid archiveId);
ValueTask<List<Web.Hutao.GachaLog.GachaItem>> GetHutaoGachaItemListAsync(Guid archiveId, GachaConfigType queryType, long endId);
ValueTask AddGachaItemRangeAsync(List<GachaItem> items);
ValueTask RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndIdAsync(Guid archiveId, GachaConfigType queryType, long endId);
} }

View File

@@ -24,10 +24,10 @@ internal sealed partial class UIGFExportService : IUIGFExportService
public async ValueTask<UIGF> ExportAsync(GachaLogServiceMetadataContext context, GachaArchive archive) public async ValueTask<UIGF> ExportAsync(GachaLogServiceMetadataContext context, GachaArchive archive)
{ {
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
List<GachaItem> entities = await gachaLogDbService
List<UIGFItem> list = gachaLogDbService .GetGachaItemListByArchiveIdAsync(archive.InnerId)
.GetGachaItemListByArchiveId(archive.InnerId) .ConfigureAwait(false);
.SelectList(i => UIGFItem.From(i, context.GetNameQualityByItemId(i.ItemId))); List<UIGFItem> list = entities.SelectList(i => UIGFItem.From(i, context.GetNameQualityByItemId(i.ItemId)));
UIGF uigf = new() UIGF uigf = new()
{ {

View File

@@ -33,15 +33,6 @@ internal sealed partial class GameDbService : IGameDbService
} }
} }
public async ValueTask UpdateGameAccountAsync(GameAccount gameAccount)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GameAccounts.UpdateAndSaveAsync(gameAccount).ConfigureAwait(false);
}
}
public void UpdateGameAccount(GameAccount gameAccount) public void UpdateGameAccount(GameAccount gameAccount)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
@@ -51,7 +42,16 @@ internal sealed partial class GameDbService : IGameDbService
} }
} }
public async ValueTask DeleteGameAccountByIdAsync(Guid id) public async ValueTask UpdateGameAccountAsync(GameAccount gameAccount)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GameAccounts.UpdateAndSaveAsync(gameAccount).ConfigureAwait(false);
}
}
public async ValueTask RemoveGameAccountByIdAsync(Guid id)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {

View File

@@ -386,16 +386,6 @@ internal sealed partial class GameService : IGameService
gameAccounts.Remove(gameAccount); gameAccounts.Remove(gameAccount);
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await gameDbService.DeleteGameAccountByIdAsync(gameAccount.InnerId).ConfigureAwait(false); await gameDbService.RemoveGameAccountByIdAsync(gameAccount.InnerId).ConfigureAwait(false);
}
private static bool LaunchSchemeMatchesExecutable(LaunchScheme launchScheme, string gameFileName)
{
return (launchScheme.IsOversea, gameFileName) switch
{
(true, GenshinImpactFileName) => true,
(false, YuanShenFileName) => true,
_ => false,
};
} }
} }

View File

@@ -10,7 +10,7 @@ internal interface IGameDbService
{ {
ValueTask AddGameAccountAsync(GameAccount gameAccount); ValueTask AddGameAccountAsync(GameAccount gameAccount);
ValueTask DeleteGameAccountByIdAsync(Guid id); ValueTask RemoveGameAccountByIdAsync(Guid id);
ObservableCollection<GameAccount> GetGameAccountCollection(); ObservableCollection<GameAccount> GetGameAccountCollection();

View File

@@ -0,0 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
namespace Snap.Hutao.Service.Inventroy;
internal interface IInventoryDbService
{
ValueTask AddInventoryItemRangeByProjectId(List<InventoryItem> items);
ValueTask RemoveInventoryItemRangeByProjectId(Guid projectId);
void UpdateInventoryItem(InventoryItem item);
ValueTask UpdateInventoryItemAsync(InventoryItem item);
}

View File

@@ -0,0 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Inventroy;
internal interface IInventoryService
{
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Snap.Hutao.Service.Inventroy;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IInventoryDbService))]
internal sealed partial class InventoryDbService : IInventoryDbService
{
private readonly IServiceProvider serviceProvider;
public async ValueTask RemoveInventoryItemRangeByProjectId(Guid projectId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.InventoryItems
.AsNoTracking()
.Where(a => a.ProjectId == projectId && a.ItemId != 202U) // 摩拉
.ExecuteDeleteAsync()
.ConfigureAwait(false);
}
}
public async ValueTask AddInventoryItemRangeByProjectId(List<InventoryItem> items)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.InventoryItems.AddRangeAndSaveAsync(items).ConfigureAwait(false);
}
}
public void UpdateInventoryItem(InventoryItem item)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.InventoryItems.UpdateAndSave(item);
}
}
public async ValueTask UpdateInventoryItemAsync(InventoryItem item)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.InventoryItems.UpdateAndSaveAsync(item).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Inventroy;
[Injection(InjectAs.Transient)]
internal sealed class InventoryService : IInventoryService
{
}

View File

@@ -9,7 +9,7 @@ internal interface IUserDbService
ValueTask DeleteUserByIdAsync(Guid id); ValueTask DeleteUserByIdAsync(Guid id);
ValueTask DeleteUsersAsync(); ValueTask RemoveUsersAsync();
ValueTask<List<Model.Entity.User>> GetUserListAsync(); ValueTask<List<Model.Entity.User>> GetUserListAsync();

View File

@@ -49,7 +49,7 @@ internal sealed partial class UserDbService : IUserDbService
} }
} }
public async ValueTask DeleteUsersAsync() public async ValueTask RemoveUsersAsync()
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {

View File

@@ -61,7 +61,7 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
public async ValueTask UnsafeRemoveUsersAsync() public async ValueTask UnsafeRemoveUsersAsync()
{ {
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
await userDbService.DeleteUsersAsync().ConfigureAwait(false); await userDbService.RemoveUsersAsync().ConfigureAwait(false);
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -79,6 +79,7 @@
<None Remove="LaunchGameWindow.xaml" /> <None Remove="LaunchGameWindow.xaml" />
<None Remove="NativeMethods.json" /> <None Remove="NativeMethods.json" />
<None Remove="NativeMethods.txt" /> <None Remove="NativeMethods.txt" />
<None Remove="Resource\BlurBackground.png" />
<None Remove="Resource\Font\CascadiaMono.ttf" /> <None Remove="Resource\Font\CascadiaMono.ttf" />
<None Remove="Resource\Font\MiSans-Regular.ttf" /> <None Remove="Resource\Font\MiSans-Regular.ttf" />
<None Remove="Resource\HutaoIconSourceTransparentBackgroundGradient1.png" /> <None Remove="Resource\HutaoIconSourceTransparentBackgroundGradient1.png" />
@@ -206,6 +207,7 @@
<!-- Resources Files --> <!-- Resources Files -->
<ItemGroup> <ItemGroup>
<Content Include="Resource\BlurBackground.png" />
<Content Include="Resource\Font\CascadiaMono.ttf" /> <Content Include="Resource\Font\CascadiaMono.ttf" />
<Content Include="Resource\Font\MiSans-Regular.ttf" /> <Content Include="Resource\Font\MiSans-Regular.ttf" />
<Content Include="Resource\HutaoIconSourceTransparentBackgroundGradient1.png" /> <Content Include="Resource\HutaoIconSourceTransparentBackgroundGradient1.png" />
@@ -239,12 +241,12 @@
<PackageReference Include="CommunityToolkit.Labs.WinUI.TokenView" Version="0.1.230809" /> <PackageReference Include="CommunityToolkit.Labs.WinUI.TokenView" Version="0.1.230809" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.TransitionHelper" Version="0.1.230809" /> <PackageReference Include="CommunityToolkit.Labs.WinUI.TransitionHelper" Version="0.1.230809" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2-build.1" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2-build.1" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.0.230828-rc" /> <PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.0.230907" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.HeaderedControls" Version="8.0.230828-rc" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.HeaderedControls" Version="8.0.230907" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.0.230828-rc" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.0.230907" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.0.230828-rc" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.0.230907" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.230828-rc" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.230907" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.230828-rc" /> <PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.230907" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Core" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Core" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10" />

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.View.Card;
internal sealed class CardReference
{
public Button? Card { get; set; }
}

View File

@@ -124,7 +124,7 @@
<StackPanel <StackPanel
Grid.Row="1" Grid.Row="1"
Margin="0,8,0,0" Margin="0,8,0,0"
Visibility="{Binding Weapon, Converter={StaticResource EmptyObjectToVisibilityConverter}}"> Visibility="{x:Bind Weapon, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Border Style="{StaticResource BorderCardStyle}"> <Border Style="{StaticResource BorderCardStyle}">
<Grid Margin="8" DataContext="{x:Bind Weapon}"> <Grid Margin="8" DataContext="{x:Bind Weapon}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>

View File

@@ -76,11 +76,7 @@
Margin="2,6,3,6" Margin="2,6,3,6"
DisplayMemberPath="Name" DisplayMemberPath="Name"
ItemsSource="{Binding Archives, Mode=OneWay}" ItemsSource="{Binding Archives, Mode=OneWay}"
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"> SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"/>
<mxi:Interaction.Behaviors>
<shcb:ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior/>
</mxi:Interaction.Behaviors>
</ComboBox>
</AppBarElementContainer> </AppBarElementContainer>
<AppBarButton <AppBarButton
Command="{Binding AddArchiveCommand}" Command="{Binding AddArchiveCommand}"

View File

@@ -5,7 +5,6 @@
xmlns:cwa="using:CommunityToolkit.WinUI.Animations" xmlns:cwa="using:CommunityToolkit.WinUI.Animations"
xmlns:cwb="using:CommunityToolkit.WinUI.Behaviors" xmlns:cwb="using:CommunityToolkit.WinUI.Behaviors"
xmlns:cwu="using:CommunityToolkit.WinUI.UI" xmlns:cwu="using:CommunityToolkit.WinUI.UI"
xmlns:cwucont="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity" xmlns:mxi="using:Microsoft.Xaml.Interactivity"
@@ -15,7 +14,6 @@
xmlns:shcb="using:Snap.Hutao.Control.Behavior" xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shci="using:Snap.Hutao.Control.Image" xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup" xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvca="using:Snap.Hutao.View.Card"
xmlns:shvco="using:Snap.Hutao.View.Control" xmlns:shvco="using:Snap.Hutao.View.Control"
xmlns:shvh="using:Snap.Hutao.ViewModel.Home" xmlns:shvh="using:Snap.Hutao.ViewModel.Home"
d:DataContext="{d:DesignInstance shvh:AnnouncementViewModel}" d:DataContext="{d:DesignInstance shvh:AnnouncementViewModel}"
@@ -29,17 +27,24 @@
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/> <shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
<DataTemplate x:Key="AnnouncementTemplate"> <DataTemplate x:Key="AnnouncementTemplate">
<cwucont:AdaptiveGridView <ItemsView
Margin="16,16,0,-4" Margin="16"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
cwa:ItemsReorderAnimation.Duration="0:0:0.1" IsItemInvokedEnabled="False"
DesiredWidth="{StaticResource AdaptiveGridViewDesiredWidth}"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemsSource="{Binding List}" ItemsSource="{Binding List}"
SelectionMode="None"> SelectionMode="None">
<cwucont:AdaptiveGridView.ItemTemplate> <ItemsView.Layout>
<UniformGridLayout
ItemsJustification="Start"
ItemsStretch="Fill"
MinColumnSpacing="12"
MinItemWidth="300"
MinRowSpacing="12"/>
</ItemsView.Layout>
<ItemsView.ItemTemplate>
<DataTemplate> <DataTemplate>
<Border cwu:UIElementExtensions.ClipToBounds="True" Style="{StaticResource BorderCardStyle}"> <ItemContainer>
<Border Style="{StaticResource BorderCardStyle}">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition/>
@@ -66,7 +71,6 @@
<!-- Time Description --> <!-- Time Description -->
<Grid Grid.Row="0"> <Grid Grid.Row="0">
<Border <Border
Height="24"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Visibility="{Binding ShouldShowTimeDescription, Converter={StaticResource BoolToVisibilityConverter}}"> Visibility="{Binding ShouldShowTimeDescription, Converter={StaticResource BoolToVisibilityConverter}}">
@@ -148,21 +152,22 @@
</mxic:EventTriggerBehavior> </mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors> </mxi:Interaction.Behaviors>
</Border> </Border>
</ItemContainer>
</DataTemplate> </DataTemplate>
</cwucont:AdaptiveGridView.ItemTemplate> </ItemsView.ItemTemplate>
</cwucont:AdaptiveGridView> </ItemsView>
</DataTemplate> </DataTemplate>
</shc:ScopedPage.Resources> </shc:ScopedPage.Resources>
<Grid> <Grid>
<ScrollViewer Padding="0,0,4,0"> <ScrollViewer Padding="0,0,0,0">
<StackPanel> <StackPanel>
<StackPanel> <StackPanel>
<TextBlock <TextBlock
Margin="16,16,12,0" Margin="16,16,16,0"
Style="{StaticResource TitleTextBlockStyle}" Style="{StaticResource TitleTextBlockStyle}"
Text="{Binding GreetingText}"/> Text="{Binding GreetingText}"/>
<TextBlock Margin="16,0,16,0" Text="{Binding UserOptions.UserName}"/> <TextBlock Margin="16,0" Text="{Binding UserOptions.UserName}"/>
<ItemsControl <ItemsControl
Margin="16,16,12,0" Margin="16,16,12,0"
@@ -190,26 +195,33 @@
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<cwucont:AdaptiveGridView <ItemsView
Margin="16,16,0,0" Margin="16"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
cwa:ItemsReorderAnimation.Duration="0:0:0.1" IsItemInvokedEnabled="False"
DesiredWidth="{StaticResource AdaptiveGridViewDesiredWidth}" ItemsSource="{Binding Cards, Mode=OneWay}"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
SelectionMode="None" SelectionMode="None"
StretchContentForSingleRow="False"> Visibility="{Binding Cards.Count, Converter={StaticResource Int32ToVisibilityConverter}}">
<shvca:LaunchGameCard Height="{StaticResource HomeAdaptiveCardHeight}"/> <ItemsView.Layout>
<shvca:GachaStatisticsCard/> <UniformGridLayout
<shvca:AchievementCard/> ItemsJustification="Start"
<shvca:DailyNoteCard/> ItemsStretch="Fill"
MinColumnSpacing="12"
<!--<Border Style="{StaticResource BorderCardStyle}"> MinItemHeight="180"
<TextBlock Text="养成计划"/> MinItemWidth="300"
</Border> MinRowSpacing="12"/>
<Border Style="{StaticResource BorderCardStyle}"> </ItemsView.Layout>
<TextBlock Text="深渊"/> <ItemsView.ItemTemplate>
</Border>--> <DataTemplate>
</cwucont:AdaptiveGridView> <ItemContainer>
<ItemContainer.Resources>
<SolidColorBrush x:Key="ItemContainerPointerOverBackground" Color="Transparent"/>
</ItemContainer.Resources>
<ContentControl HorizontalContentAlignment="Stretch" Content="{Binding Card}"/>
</ItemContainer>
</DataTemplate>
</ItemsView.ItemTemplate>
</ItemsView>
</StackPanel> </StackPanel>
<Pivot Style="{StaticResource DefaultPivotStyle}"> <Pivot Style="{StaticResource DefaultPivotStyle}">

View File

@@ -66,11 +66,7 @@
Margin="6,6,6,6" Margin="6,6,6,6"
DisplayMemberPath="Name" DisplayMemberPath="Name"
ItemsSource="{Binding Projects}" ItemsSource="{Binding Projects}"
SelectedItem="{Binding SelectedProject, Mode=TwoWay}"> SelectedItem="{Binding SelectedProject, Mode=TwoWay}"/>
<mxi:Interaction.Behaviors>
<shcb:ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior/>
</mxi:Interaction.Behaviors>
</ComboBox>
</AppBarElementContainer> </AppBarElementContainer>
<AppBarButton <AppBarButton
Command="{Binding AddProjectCommand}" Command="{Binding AddProjectCommand}"

View File

@@ -36,11 +36,7 @@
Margin="16,6,0,6" Margin="16,6,0,6"
DisplayMemberPath="Uid" DisplayMemberPath="Uid"
ItemsSource="{Binding Archives}" ItemsSource="{Binding Archives}"
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"> SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"/>
<mxi:Interaction.Behaviors>
<shcb:ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior/>
</mxi:Interaction.Behaviors>
</ComboBox>
</Pivot.LeftHeader> </Pivot.LeftHeader>
<Pivot.RightHeader> <Pivot.RightHeader>
<CommandBar DefaultLabelPosition="Right"> <CommandBar DefaultLabelPosition="Right">

View File

@@ -23,9 +23,9 @@
<Grid> <Grid>
<Image <Image
VerticalAlignment="Center" VerticalAlignment="Center"
Source="ms-appx:///Assets/Square44x44Logo.targetsize-256.png" Source="ms-appx:///Resource/BlurBackground.png"
Stretch="UniformToFill"/> Stretch="Fill"/>
<Grid Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"> <Grid Background="{ThemeResource SystemControlBackgroundAltMediumBrush}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
@@ -91,6 +91,8 @@
Description="{Binding HutaoOptions.WebView2Version}" Description="{Binding HutaoOptions.WebView2Version}"
Header="{shcm:ResourceString Name=ViewPageSettingWebview2Header}" Header="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"
HeaderIcon="{shcm:FontIcon Glyph=&#xECAA;}"/> HeaderIcon="{shcm:FontIcon Glyph=&#xECAA;}"/>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingShellExperienceHeader}"/>
<cwc:SettingsCard <cwc:SettingsCard
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCreateDesktopShortcutAction}" ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCreateDesktopShortcutAction}"
Command="{Binding CreateDesktopShortcutCommand}" Command="{Binding CreateDesktopShortcutCommand}"
@@ -119,28 +121,68 @@
SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/> SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/>
</cwc:SettingsCard> </cwc:SettingsCard>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewpageSettingHomeHeader}"/>
<cwc:SettingsExpander
Description="{shcm:ResourceString Name=ViewpageSettingHomeCardDescription}"
Header="{shcm:ResourceString Name=ViewpageSettingHomeCardHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xEE40;}">
<cwc:SettingsExpander.Items>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewpageSettingHomeCardItemLaunchGameHeader}">
<ToggleSwitch
IsOn="{Binding HomeCardOptions.IsHomeCardLaunchGamePresented, Mode=TwoWay}"
OffContent="{shcm:ResourceString Name=ViewPageSettingHomeCardOff}"
OnContent="{shcm:ResourceString Name=ViewPageSettingHomeCardOn}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewpageSettingHomeCardItemgachaStatisticsHeader}">
<ToggleSwitch
IsOn="{Binding HomeCardOptions.IsHomeCardGachaStatisticsPresented, Mode=TwoWay}"
OffContent="{shcm:ResourceString Name=ViewPageSettingHomeCardOff}"
OnContent="{shcm:ResourceString Name=ViewPageSettingHomeCardOn}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewpageSettingHomeCardItemAchievementHeader}">
<ToggleSwitch
IsOn="{Binding HomeCardOptions.IsHomeCardAchievementPresented, Mode=TwoWay}"
OffContent="{shcm:ResourceString Name=ViewPageSettingHomeCardOff}"
OnContent="{shcm:ResourceString Name=ViewPageSettingHomeCardOn}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewpageSettingHomeCardItemDailyNoteHeader}">
<ToggleSwitch
IsOn="{Binding HomeCardOptions.IsHomeCardDailyNotePresented, Mode=TwoWay}"
OffContent="{shcm:ResourceString Name=ViewPageSettingHomeCardOff}"
OnContent="{shcm:ResourceString Name=ViewPageSettingHomeCardOn}"/>
</cwc:SettingsCard>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingGameHeader}"/> <TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingGameHeader}"/>
<InfoBar
IsClosable="False"
IsOpen="True"
Message="{shcm:ResourceString Name=ViewPageSettingSetGamePathHint}"
Severity="Informational"/>
<cwc:SettingsCard <cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}" ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingSetGamePathAction}" ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingSetGamePathAction}"
Command="{Binding SetGamePathCommand}" Command="{Binding SetGamePathCommand}"
Description="{Binding Options.GamePath}"
Header="{shcm:ResourceString Name=ViewPageSettingSetGamePathHeader}" Header="{shcm:ResourceString Name=ViewPageSettingSetGamePathHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE7FC;}" HeaderIcon="{shcm:FontIcon Glyph=&#xE7FC;}"
IsClickEnabled="True"/> IsClickEnabled="True">
<cwc:SettingsCard.Description>
<StackPanel>
<TextBlock Foreground="{ThemeResource SystemErrorTextColor}" Text="{shcm:ResourceString Name=ViewPageSettingSetGamePathHint}"/>
<TextBlock Text="{Binding Options.GamePath}"/>
</StackPanel>
</cwc:SettingsCard.Description>
</cwc:SettingsCard>
<cwc:SettingsCard <cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}" ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingSetGamePathAction}" ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingSetGamePathAction}"
Command="{Binding SetPowerShellPathCommand}" Command="{Binding SetPowerShellPathCommand}"
Description="{Binding Options.PowerShellPath}"
Header="{shcm:ResourceString Name=ViewPageSettingSetPowerShellPathHeader}" Header="{shcm:ResourceString Name=ViewPageSettingSetPowerShellPathHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE756;}" HeaderIcon="{shcm:FontIcon Glyph=&#xE756;}"
IsClickEnabled="True"/> IsClickEnabled="True">
<cwc:SettingsCard.Description>
<StackPanel>
<TextBlock Text="{shcm:ResourceString Name=ViewPageSettingSetPowerShellDescription}"/>
<TextBlock Text="{Binding Options.PowerShellPath}"/>
</StackPanel>
</cwc:SettingsCard.Description>
</cwc:SettingsCard>
<cwc:SettingsCard <cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}" ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingDeleteCacheAction}" ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingDeleteCacheAction}"

View File

@@ -315,6 +315,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
if (result.Interrupted) if (result.Interrupted)
{ {
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
infoBarService.Warning(SH.ViewModelCultivationBatchAddIncompletedFormat.Format(result.SucceedCount, result.SkippedCount)); infoBarService.Warning(SH.ViewModelCultivationBatchAddIncompletedFormat.Format(result.SucceedCount, result.SkippedCount));
} }
else else

View File

@@ -1,9 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Hutao; using Snap.Hutao.Service.Hutao;
using Snap.Hutao.View.Card;
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -25,6 +27,7 @@ internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel
private AnnouncementWrapper? announcement; private AnnouncementWrapper? announcement;
private string greetingText = SH.ViewPageHomeGreetingTextDefault; private string greetingText = SH.ViewPageHomeGreetingTextDefault;
private ObservableCollection<Web.Hutao.HutaoAsAService.Announcement>? hutaoAnnouncements; private ObservableCollection<Web.Hutao.HutaoAsAService.Announcement>? hutaoAnnouncements;
private List<CardReference>? cards;
/// <summary> /// <summary>
/// 公告 /// 公告
@@ -43,8 +46,11 @@ internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel
/// </summary> /// </summary>
public string GreetingText { get => greetingText; set => SetProperty(ref greetingText, value); } public string GreetingText { get => greetingText; set => SetProperty(ref greetingText, value); }
public List<CardReference>? Cards { get => cards; set => SetProperty(ref cards, value); }
protected override ValueTask<bool> InitializeUIAsync() protected override ValueTask<bool> InitializeUIAsync()
{ {
InitializeDashboard();
InitializeInGameAnnouncementAsync().SafeForget(); InitializeInGameAnnouncementAsync().SafeForget();
InitializeHutaoAnnouncementAsync().SafeForget(); InitializeHutaoAnnouncementAsync().SafeForget();
UpdateGreetingText(); UpdateGreetingText();
@@ -105,4 +111,31 @@ internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel
} }
} }
} }
private void InitializeDashboard()
{
List<CardReference> result = new();
if (LocalSetting.Get(SettingKeys.IsHomeCardLaunchGamePresented, true))
{
result.Add(new() { Card = new LaunchGameCard() });
}
if (LocalSetting.Get(SettingKeys.IsHomeCardGachaStatisticsPresented, true))
{
result.Add(new() { Card = new GachaStatisticsCard() });
}
if (LocalSetting.Get(SettingKeys.IsHomeCardAchievementPresented, true))
{
result.Add(new() { Card = new AchievementCard() });
}
if (LocalSetting.Get(SettingKeys.IsHomeCardDailyNotePresented, true))
{
result.Add(new() { Card = new DailyNoteCard() });
}
Cards = result;
}
} }

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Setting;
namespace Snap.Hutao.ViewModel;
internal sealed class HomeCardOptions
{
public bool IsHomeCardLaunchGamePresented
{
get => LocalSetting.Get(SettingKeys.IsHomeCardLaunchGamePresented, true);
set => LocalSetting.Set(SettingKeys.IsHomeCardLaunchGamePresented, value);
}
public bool IsHomeCardGachaStatisticsPresented
{
get => LocalSetting.Get(SettingKeys.IsHomeCardGachaStatisticsPresented, true);
set => LocalSetting.Set(SettingKeys.IsHomeCardGachaStatisticsPresented, value);
}
public bool IsHomeCardAchievementPresented
{
get => LocalSetting.Get(SettingKeys.IsHomeCardAchievementPresented, true);
set => LocalSetting.Set(SettingKeys.IsHomeCardAchievementPresented, value);
}
public bool IsHomeCardDailyNotePresented
{
get => LocalSetting.Get(SettingKeys.IsHomeCardDailyNotePresented, true);
set => LocalSetting.Set(SettingKeys.IsHomeCardDailyNotePresented, value);
}
}

View File

@@ -37,6 +37,8 @@ namespace Snap.Hutao.ViewModel;
[Injection(InjectAs.Scoped)] [Injection(InjectAs.Scoped)]
internal sealed partial class SettingViewModel : Abstraction.ViewModel internal sealed partial class SettingViewModel : Abstraction.ViewModel
{ {
private readonly HomeCardOptions homeCardOptions = new();
private readonly IContentDialogFactory contentDialogFactory; private readonly IContentDialogFactory contentDialogFactory;
private readonly IGameLocatorFactory gameLocatorFactory; private readonly IGameLocatorFactory gameLocatorFactory;
private readonly INavigationService navigationService; private readonly INavigationService navigationService;
@@ -68,6 +70,8 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
/// </summary> /// </summary>
public HutaoUserOptions UserOptions { get => hutaoUserOptions; } public HutaoUserOptions UserOptions { get => hutaoUserOptions; }
public HomeCardOptions HomeCardOptions { get => homeCardOptions; }
/// <summary> /// <summary>
/// 选中的背景类型 /// 选中的背景类型
/// </summary> /// </summary>