Compare commits

..

43 Commits

Author SHA1 Message Date
DismissedLight
a6971042dc Merge pull request #1746 from DGP-Studio/develop 2024-06-19 15:46:10 +08:00
Masterain
87f1f2c46b New Crowdin updates (#1743) 2024-06-19 14:54:26 +08:00
Lightczx
c576d8f7c4 bump version 2024-06-19 14:54:03 +08:00
Lightczx
a0c1241b32 spiral abyss delta view 2024-06-18 16:34:28 +08:00
DismissedLight
a3ab24554a add delta rate 2024-06-18 00:07:31 +08:00
DismissedLight
9ae45a4cc4 Merge pull request #1735 from DGP-Studio/qa 2024-06-17 21:39:01 +08:00
DismissedLight
f700faae14 Merge pull request #1737 from DGP-Studio/feat/hypapi 2024-06-17 21:33:06 +08:00
DismissedLight
57b51ed5ee code style 2024-06-17 21:32:51 +08:00
qhy040404
5dfb7fbb63 rename 2024-06-17 18:42:07 +08:00
qhy040404
046823245c refactor package converter 2024-06-17 18:42:06 +08:00
qhy040404
0497d89559 refactor launchgame resources 2024-06-17 18:42:06 +08:00
qhy040404
9d364a291c minor change 2024-06-17 18:42:06 +08:00
qhy040404
c342147809 Add HoyoPlay API 2024-06-17 18:42:05 +08:00
Lightczx
a86caaf229 fix build 2024-06-17 17:32:01 +08:00
Lightczx
d0b07f1308 spiral abyss last data 2024-06-17 17:07:03 +08:00
qhy040404
409a223213 fix inventory items wrong position 2024-06-17 10:36:42 +08:00
Lightczx
75ea2b807f Update AsyncBarrier.cs 2024-06-17 09:57:21 +08:00
qhy040404
719d934222 fix part of qa issues 2024-06-17 00:26:45 +08:00
DismissedLight
e8eed46d82 Merge pull request #1725 from DGP-Studio/feat/gamerole_profilepicture 2024-06-16 22:46:47 +08:00
DismissedLight
ff9b553a19 code style 2024-06-16 22:46:24 +08:00
qhy040404
95d64c2895 make UserGameRole observable 2024-06-16 20:41:19 +08:00
qhy040404
558551c8ad rename 2024-06-16 19:14:33 +08:00
qhy040404
d05c196b7c apply changes 2024-06-16 19:04:51 +08:00
DismissedLight
502fb6dbed fix notification activation 2024-06-16 17:27:20 +08:00
qhy040404
4fa5270070 fix failed notification activate 2024-06-16 01:43:43 +08:00
DismissedLight
94fda223fc drop notification 2024-06-16 01:25:52 +08:00
Mikachu2333
18103b4deb modify alpha's tip (#1730)
Co-authored-by: LinkChou <linkchou@yandex.com>
2024-06-16 01:21:36 +08:00
qhy040404
16ac52e71d use list instead of mappppps 2024-06-16 01:19:56 +08:00
qhy040404
73825d391e fix build 2024-06-16 00:47:02 +08:00
qhy040404
3b2eeb84a7 add profile picture for each game role 2024-06-16 00:46:55 +08:00
DismissedLight
3e8655fd55 Merge pull request #1722 from DGP-Studio/feat/window
make windows transient
2024-06-15 16:04:58 +08:00
DismissedLight
fe38e14ae8 code style 2024-06-15 15:54:04 +08:00
DismissedLight
a174493819 Merge branch 'feat/window' of https://github.com/DGP-Studio/Snap.Hutao into feat/window 2024-06-15 14:32:03 +08:00
qhy040404
3a57d55c62 make windows transient 2024-06-15 14:31:29 +08:00
DismissedLight
99f35ca6db avatar property grid view rework 2024-06-15 00:43:59 +08:00
DismissedLight
c423e8b72d ProfilePicture add unlock type 2024-06-14 23:18:11 +08:00
DismissedLight
7ff78def46 fix hotkey can't register 2024-06-14 11:05:56 +08:00
qhy040404
bc9018f4bf make windows transient 2024-06-13 19:48:06 +08:00
qhy040404
3513268ad9 Update issue template 2024-06-13 18:39:29 +08:00
qhy040404
107963b7ac Update issue template 2024-06-13 18:39:03 +08:00
DismissedLight
4e89406f2f Merge pull request #1721 from DGP-Studio/feat/1715 2024-06-13 16:15:21 +08:00
Masterain
850ea7ed4b Update README.md (#1717) 2024-06-11 20:50:44 -07:00
DismissedLight
fd59b471cb Merge pull request #1701 from DGP-Studio/develop 2024-06-05 22:12:35 +08:00
172 changed files with 2702 additions and 1718 deletions

View File

@@ -19,7 +19,7 @@ body:
- label: 我已阅读 Snap Hutao 文档中的[常见问题](https://hut.ao/advanced/FAQ.html)和[常见程序异常](https://hut.ao/advanced/exceptions.html),我的问题没有在文档中得到解答
required: true
- label: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
- label: 我知道[文档站](https://hut.ao/zh/menu.html)的导航栏中有**搜索功能**,且已经搜索过相关关键词
required: true
- label: 我的问题不是[已完成](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E5%AE%8C%E6%88%90)的问题也不是一个别人已发布的**重复的**问题

View File

@@ -1,7 +1,7 @@
name: 功能请求
name: 功能请求
description: 通过这个议题来向开发团队分享你的想法
title: "[Feat]: 在这里填写一个合适的标题"
labels: ["功能", "needs-triage", "priority:none"]
labels: ["feature request", "needs-triage", "priority:none"]
assignees:
- Lightczx
body:
@@ -24,4 +24,4 @@ body:
label: 想要实现或优化的功能
description: 详细的描述一下你想要的功能,描述的越具体,采纳的可能性越高
validations:
required: true
required: true

View File

@@ -1,7 +1,7 @@
name: Feature Request [English Form]
description: Tell us about your thought
title: "[Feat]: Place your title here"
labels: ["功能", "needs-triage", "priority:none"]
labels: ["feature request", "needs-triage", "priority:none"]
assignees:
- Lightczx
body:
@@ -22,6 +22,6 @@ body:
id: req
attributes:
label: Detail of the Feature
description: Descripbe the feaure in detail. The more detailed and convincing the desciprtion the more likyly feature will be accepted.
description: Descripbe the feaure in detail. The more detailed and convincing the desciprtion the more likyly feature will be accepted.
validations:
required: true
required: true

View File

@@ -64,12 +64,10 @@ jobs:
> 该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**
> [!TIP]
> 普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
> 普通用户请 [点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/) 下载最新的稳定版本
> [!IMPORTANT]
> 请注意,从 Snap Hutao Alpha 2023.12.21.3 开始,我们将使用全新的 CI 证书,原有的 Snap.Hutao.CI.cer 将在几天后过期停止使用。
>
> 请安装 [DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt) 到 `受信任的根证书颁发机构` 以安装测试版安装包
> 请先安装 **[DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt)** 到 **受信任的根证书颁发机构** 以安装测试版安装包
"
echo $summary >> $Env:GITHUB_STEP_SUMMARY
@@ -111,12 +109,10 @@ jobs:
> 该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**
> [!TIP]
> 普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
> 普通用户请 [点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/) 下载最新的稳定版本
> [!IMPORTANT]
> 请注意,从 Snap Hutao Alpha 2023.12.21.3 开始,我们将使用全新的 CI 证书,原有的 Snap.Hutao.CI.cer 将在几天后过期停止使用。
>
> 请安装 [DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt) 到 `受信任的根证书颁发机构` 以安装测试版安装包
> 请先安装 **[DGP_Studio_CA.crt](https://github.com/DGP-Automation/Hutao-Auto-Release/releases/download/certificate-ca/DGP_Studio_CA.crt)** 到 **受信任的根证书颁发机构** 以安装测试版安装包
"
echo $summary >> $Env:GITHUB_STEP_SUMMARY

View File

@@ -72,9 +72,9 @@ Snap Hutao uses [Crowdin](https://translate.hut.ao/) as a client text translatio
Snap Hutao is currently using sponsored software from the following service providers.
| [![](https://www.netlify.com/v3/img/components/netlify-light.svg)](https://www.netlify.com/) | [![](https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg)](https://crowdin.com/) | [![](https://gitlab.cn/images/icons/logos/logo-121-75.svg)](https://gitlab.cn/) |
|:-:|:-:|:-:|
|[![](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/73ae8b90-f3c7-4033-b2b7-f4126331ce66)](https://about.signpath.io)|[![](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/49aed8ee-9f19-4a8a-998c-7b93ee286d65)](https://1password.com/)|[![](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/ad121220-d2d3-4f49-b215-b6d063dc229d)](https://www.digitalocean.com)|
|[![jetbrains](https://github.com/DGP-Studio/Snap.Hutao/assets/36357191/4105772a-728a-4a84-9c6e-d713a5698a20)](https://www.jetbrains.com/opensource/)|||
| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
| [![](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/73ae8b90-f3c7-4033-b2b7-f4126331ce66)](https://about.signpath.io) | [![](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/49aed8ee-9f19-4a8a-998c-7b93ee286d65)](https://1password.com/) | [![](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/ad121220-d2d3-4f49-b215-b6d063dc229d)](https://www.digitalocean.com) |
| [![ducalis](https://raw.githubusercontent.com/DGP-Studio/Snap.Hutao.Docs/main/docs/.vuepress/public/svg/ducalis.svg)](https://hi.ducalis.io/) | [![jetbrains](https://github.com/DGP-Studio/Snap.Hutao/assets/36357191/4105772a-728a-4a84-9c6e-d713a5698a20)](https://www.jetbrains.com/opensource/) | |
- Netlify provides document and home page hosting service for Snap Hutao
@@ -88,6 +88,8 @@ Snap Hutao is currently using sponsored software from the following service prov
- DigitalOcean provides reliable cloud database for Snap Hutao database backup
- [Ducalis.io](https://hi.ducalis.io/) provides Snap Hutao project with a complete decision-making toolkit for project management
- Jetbrains provides powerful IDE for Snap Hutao infrastructure services coding
## 开发 / Development

View File

@@ -31,6 +31,7 @@
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///View/Card/Primitive/CardProgressBar.xaml"/>
<ResourceDictionary Source="ms-appx:///View/Control/RateDeltaTextBlockStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style

View File

@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using Microsoft.Windows.AppNotifications;
using Snap.Hutao.Core;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.LifeCycle;
@@ -68,6 +69,10 @@ public sealed partial class App : Application
{
try
{
// Important: You must call AppNotificationManager::Default().Register
// before calling AppInstance.GetCurrent.GetActivatedEventArgs.
AppNotificationManager.Default.NotificationInvoked += activation.NotificationInvoked;
AppNotificationManager.Default.Register();
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
if (serviceProvider.GetRequiredService<PrivateNamedPipeClient>().TryRedirectActivationTo(activatedEventArgs))
@@ -81,14 +86,7 @@ public sealed partial class App : Application
LogDiagnosticInformation();
// Manually invoke
HutaoActivationArguments hutaoArgs = HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs);
if (hutaoArgs.Kind is HutaoActivationKind.Toast)
{
Exit();
return;
}
activation.Activate(hutaoArgs);
activation.Activate(HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs));
activation.PostInitialization();
}
catch (Exception ex)

View File

@@ -21,8 +21,6 @@ namespace Snap.Hutao.Control.Image;
[DependencyProperty("CachedName", typeof(string), "Unknown")]
internal sealed partial class CachedImage : Implementation.ImageEx
{
private string? file;
/// <summary>
/// 构造一个新的缓存图像
/// </summary>
@@ -43,7 +41,6 @@ internal sealed partial class CachedImage : Implementation.ImageEx
HutaoException.ThrowIf(string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri);
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread.
CachedName = Path.GetFileName(file);
this.file = file;
token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled.
return file.ToUri();
}

View File

@@ -24,7 +24,7 @@
x:Name="ContentGrid"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
x:Load="False">
x:Load="True">
<ContentPresenter.RenderTransform>
<CompositeTransform/>
</ContentPresenter.RenderTransform>

View File

@@ -10,6 +10,7 @@
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
<shmmc:AvatarCardConverter x:Key="AvatarCardConverter"/>
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
<shmmc:AvatarIconCircleConverter x:Key="AvatarIconCircleConverter"/>
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
<shmmc:DescriptionsParametersDescriptor x:Key="DescParamDescriptor"/>

View File

@@ -2,4 +2,5 @@
<CornerRadius x:Key="ControlCornerRadiusTop">4,4,0,0</CornerRadius>
<CornerRadius x:Key="ControlCornerRadiusBottom">0,0,4,4</CornerRadius>
<CornerRadius x:Key="ControlCornerRadiusTopRightAndBottomLeft">0,4,0,4</CornerRadius>
<CornerRadius x:Key="CornerRadiusAll16">16</CornerRadius>
</ResourceDictionary>

View File

@@ -23,6 +23,9 @@
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing4Template">
<StackPanel Orientation="Horizontal" Spacing="4"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing6Template">
<StackPanel Orientation="Horizontal" Spacing="6"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="StackPanelSpacing4Template">
<StackPanel Spacing="4"/>
</ItemsPanelTemplate>

View File

@@ -1,9 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Notifications;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppNotifications;
using Snap.Hutao.Core.LifeCycle.InterProcess;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Shell;
@@ -11,7 +11,6 @@ using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Core.Windowing.HotKey;
using Snap.Hutao.Core.Windowing.NotifyIcon;
using Snap.Hutao.Service;
using Snap.Hutao.Service.DailyNote;
using Snap.Hutao.Service.Discord;
using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Job;
@@ -37,12 +36,10 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
public const string ImportUIAFFromClipboard = nameof(ImportUIAFFromClipboard);
private const string CategoryAchievement = "ACHIEVEMENT";
private const string CategoryDailyNote = "DAILYNOTE";
private const string UrlActionImport = "/IMPORT";
private const string UrlActionRefresh = "/REFRESH";
private readonly IServiceProvider serviceProvider;
private readonly ICurrentXamlWindowReference currentWindowReference;
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly SemaphoreSlim activateSemaphore = new(1);
@@ -50,7 +47,47 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
/// <inheritdoc/>
public void Activate(HutaoActivationArguments args)
{
HandleActivationAsync(args).SafeForget();
HandleActivationExclusiveAsync(args).SafeForget();
async ValueTask HandleActivationExclusiveAsync(HutaoActivationArguments args)
{
await taskContext.SwitchToBackgroundAsync();
if (activateSemaphore.CurrentCount > 0)
{
using (await activateSemaphore.EnterAsync().ConfigureAwait(false))
{
switch (args.Kind)
{
case HutaoActivationKind.Protocol:
{
ArgumentNullException.ThrowIfNull(args.ProtocolActivatedUri);
await HandleProtocolActivationAsync(args.ProtocolActivatedUri, args.IsRedirectTo).ConfigureAwait(false);
break;
}
case HutaoActivationKind.Launch:
{
ArgumentNullException.ThrowIfNull(args.LaunchActivatedArguments);
await HandleLaunchActivationAsync(args.IsRedirectTo).ConfigureAwait(false);
break;
}
case HutaoActivationKind.AppNotification:
{
ArgumentNullException.ThrowIfNull(args.AppNotificationActivatedArguments);
await HandleAppNotificationActivationAsync(args.AppNotificationActivatedArguments, args.IsRedirectTo).ConfigureAwait(false);
break;
}
}
}
}
}
}
public void NotificationInvoked(AppNotificationManager manager, AppNotificationActivatedEventArgs args)
{
HandleAppNotificationActivationAsync(args.Arguments, false).SafeForget();
}
/// <inheritdoc/>
@@ -62,20 +99,23 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
{
await taskContext.SwitchToBackgroundAsync();
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget();
ToastNotificationManagerCompat.OnActivated += NotificationActivate;
using (await activateSemaphore.EnterAsync().ConfigureAwait(false))
{
// TODO: Introduced in 1.10.2, remove in later version
serviceProvider.GetRequiredService<IJumpListInterop>().ClearAsync().SafeForget();
serviceProvider.GetRequiredService<IScheduleTaskInterop>().UnregisterAllTasks();
{
serviceProvider.GetRequiredService<IJumpListInterop>().ClearAsync().SafeForget();
serviceProvider.GetRequiredService<IScheduleTaskInterop>().UnregisterAllTasks();
}
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) < GuideState.Completed)
{
return;
}
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget();
// RegisterHotKey should be called from main thread
await taskContext.SwitchToMainThreadAsync();
serviceProvider.GetRequiredService<HotKeyOptions>().RegisterAll();
if (serviceProvider.GetRequiredService<AppOptions>().IsNotifyIconEnabled)
@@ -87,7 +127,18 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
_ = serviceProvider.GetRequiredService<NotifyIconController>();
}
serviceProvider.GetRequiredService<IQuartzService>().StartAsync(default).SafeForget();
serviceProvider.GetRequiredService<IDiscordService>().SetNormalActivityAsync().SafeForget();
serviceProvider.GetRequiredService<IQuartzService>().StartAsync().SafeForget();
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
{
metadataServiceInitialization.InitializeInternalAsync().SafeForget();
}
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
{
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
}
}
}
}
@@ -133,55 +184,52 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
}
}
private void NotificationActivate(ToastNotificationActivatedEventArgsCompat args)
private async ValueTask HandleProtocolActivationAsync(Uri uri, bool isRedirectTo)
{
ToastArguments toastArgs = ToastArguments.Parse(args.Argument);
UriBuilder builder = new(uri);
if (toastArgs.TryGetValue(Action, out string? action))
{
if (action == LaunchGame)
{
_ = toastArgs.TryGetValue(Uid, out string? uid);
HandleLaunchGameActionAsync(uid).SafeForget();
}
}
}
string category = builder.Host.ToUpperInvariant();
string action = builder.Path.ToUpperInvariant();
private async ValueTask HandleActivationAsync(HutaoActivationArguments args)
{
await taskContext.SwitchToBackgroundAsync();
if (activateSemaphore.CurrentCount > 0)
// string parameter = builder.Query.ToUpperInvariant();
switch (category)
{
using (await activateSemaphore.EnterAsync().ConfigureAwait(false))
{
await HandleActivationCoreAsync(args).ConfigureAwait(false);
}
}
}
private async ValueTask HandleActivationCoreAsync(HutaoActivationArguments args)
{
if (args.Kind is HutaoActivationKind.Protocol)
{
ArgumentNullException.ThrowIfNull(args.ProtocolActivatedUri);
await HandleUrlActivationAsync(args.ProtocolActivatedUri, args.IsRedirectTo).ConfigureAwait(false);
}
else if (args.Kind is HutaoActivationKind.Launch)
{
ArgumentNullException.ThrowIfNull(args.LaunchActivatedArguments);
switch (args.LaunchActivatedArguments)
{
default:
case CategoryAchievement:
{
await WaitMainWindowOrCurrentAsync().ConfigureAwait(false);
if (currentWindowReference.Window is not MainWindow)
{
await HandleNormalLaunchActionAsync(args.IsRedirectTo).ConfigureAwait(false);
break;
// TODO: Send notification to hint?
return;
}
}
switch (action)
{
case UrlActionImport:
{
await taskContext.SwitchToMainThreadAsync();
INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipboard);
await serviceProvider
.GetRequiredService<INavigationService>()
.NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true)
.ConfigureAwait(false);
break;
}
}
break;
}
default:
{
await HandleLaunchActivationAsync(isRedirectTo).ConfigureAwait(false);
break;
}
}
}
private async ValueTask HandleNormalLaunchActionAsync(bool isRedirectTo)
private async ValueTask HandleLaunchActivationAsync(bool isRedirectTo)
{
if (!isRedirectTo)
{
@@ -213,6 +261,22 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
await WaitMainWindowOrCurrentAsync().ConfigureAwait(false);
}
private async ValueTask HandleAppNotificationActivationAsync(IDictionary<string, string> arguments, bool isRedirectTo)
{
if (arguments.TryGetValue(Action, out string? action))
{
if (action == LaunchGame)
{
_ = arguments.TryGetValue(Uid, out string? uid);
await HandleLaunchGameActionAsync(uid).ConfigureAwait(false);
}
}
else
{
await HandleLaunchActivationAsync(isRedirectTo).ConfigureAwait(false);
}
}
private async ValueTask WaitMainWindowOrCurrentAsync()
{
if (currentWindowReference.Window is { } window)
@@ -229,100 +293,5 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
mainWindow.SwitchTo();
mainWindow.BringToForeground();
await taskContext.SwitchToBackgroundAsync();
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
{
metadataServiceInitialization.InitializeInternalAsync().SafeForget();
}
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
{
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
}
serviceProvider.GetRequiredService<IDiscordService>().SetNormalActivityAsync().SafeForget();
}
private async ValueTask HandleUrlActivationAsync(Uri uri, bool isRedirectTo)
{
UriBuilder builder = new(uri);
string category = builder.Host.ToUpperInvariant();
string action = builder.Path.ToUpperInvariant();
string parameter = builder.Query.ToUpperInvariant();
switch (category)
{
case CategoryAchievement:
{
await WaitMainWindowOrCurrentAsync().ConfigureAwait(false);
await HandleAchievementActionAsync(action, parameter, isRedirectTo).ConfigureAwait(false);
break;
}
case CategoryDailyNote:
{
await HandleDailyNoteActionAsync(action, parameter, isRedirectTo).ConfigureAwait(false);
break;
}
default:
{
await HandleNormalLaunchActionAsync(isRedirectTo).ConfigureAwait(false);
break;
}
}
}
private async ValueTask HandleAchievementActionAsync(string action, string parameter, bool isRedirectTo)
{
_ = parameter;
_ = isRedirectTo;
switch (action)
{
case UrlActionImport:
{
await taskContext.SwitchToMainThreadAsync();
INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipboard);
await serviceProvider
.GetRequiredService<INavigationService>()
.NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true)
.ConfigureAwait(false);
break;
}
}
}
private async ValueTask HandleDailyNoteActionAsync(string action, string parameter, bool isRedirectTo)
{
_ = parameter;
switch (action)
{
case UrlActionRefresh:
{
try
{
await serviceProvider
.GetRequiredService<IDailyNoteService>()
.RefreshDailyNotesAsync()
.ConfigureAwait(false);
}
catch
{
}
// Check if it's redirected.
if (!isRedirectTo)
{
// It's a direct open process, should exit immediately.
Process.GetCurrentProcess().Kill();
}
break;
}
}
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.Windows.AppLifecycle;
using Microsoft.Windows.AppNotifications;
using Windows.ApplicationModel.Activation;
namespace Snap.Hutao.Core.LifeCycle;
@@ -12,12 +13,6 @@ namespace Snap.Hutao.Core.LifeCycle;
[HighQuality]
internal static class AppActivationArgumentsExtensions
{
/// <summary>
/// 尝试获取协议启动的Uri
/// </summary>
/// <param name="activatedEventArgs">应用程序激活参数</param>
/// <param name="uri">协议Uri</param>
/// <returns>是否存在协议Uri</returns>
public static bool TryGetProtocolActivatedUri(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out Uri? uri)
{
uri = null;
@@ -30,15 +25,10 @@ internal static class AppActivationArgumentsExtensions
return true;
}
/// <summary>
/// 尝试获取启动的参数
/// </summary>
/// <param name="activatedEventArgs">应用程序激活参数</param>
/// <param name="arguments">参数</param>
/// <returns>是否存在参数</returns>
public static bool TryGetLaunchActivatedArguments(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
{
arguments = null;
if (activatedEventArgs.Data is not ILaunchActivatedEventArgs launchArgs)
{
return false;
@@ -47,4 +37,21 @@ internal static class AppActivationArgumentsExtensions
arguments = launchArgs.Arguments.Trim();
return true;
}
public static bool TryGetAppNotificationActivatedArguments(this AppActivationArguments activatedEventArgs, out string? argument, [NotNullWhen(true)] out IDictionary<string, string>? arguments, [NotNullWhen(true)] out IDictionary<string, string>? userInput)
{
argument = null;
arguments = null;
userInput = null;
if (activatedEventArgs.Data is not AppNotificationActivatedEventArgs appNotificationArgs)
{
return false;
}
argument = appNotificationArgs.Argument;
arguments = appNotificationArgs.Arguments;
userInput = appNotificationArgs.UserInput;
return true;
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.Primitives;
using Microsoft.Windows.AppLifecycle;
namespace Snap.Hutao.Core.LifeCycle;
@@ -10,14 +9,16 @@ internal sealed class HutaoActivationArguments
{
public bool IsRedirectTo { get; set; }
public bool IsToastActivated { get; set; }
public HutaoActivationKind Kind { get; set; }
public Uri? ProtocolActivatedUri { get; set; }
public string? LaunchActivatedArguments { get; set; }
public IDictionary<string, string>? AppNotificationActivatedArguments { get; set; }
public IDictionary<string, string>? AppNotificationActivatedUserInput { get; set; }
public static HutaoActivationArguments FromAppActivationArguments(AppActivationArguments args, bool isRedirected = false)
{
HutaoActivationArguments result = new()
@@ -33,15 +34,6 @@ internal sealed class HutaoActivationArguments
if (args.TryGetLaunchActivatedArguments(out string? arguments))
{
result.LaunchActivatedArguments = arguments;
foreach (StringSegment segment in new StringTokenizer(arguments, [' ']))
{
if (segment.AsSpan().SequenceEqual("-ToastActivated"))
{
result.Kind = HutaoActivationKind.Toast;
break;
}
}
}
break;
@@ -55,6 +47,19 @@ internal sealed class HutaoActivationArguments
result.ProtocolActivatedUri = uri;
}
break;
}
case ExtendedActivationKind.AppNotification:
{
result.Kind = HutaoActivationKind.AppNotification;
if (args.TryGetAppNotificationActivatedArguments(out string? argument, out IDictionary<string, string>? arguments, out IDictionary<string, string>? userInput))
{
result.LaunchActivatedArguments = argument;
result.AppNotificationActivatedArguments = arguments;
result.AppNotificationActivatedUserInput = userInput;
}
break;
}
}

View File

@@ -7,6 +7,6 @@ internal enum HutaoActivationKind
{
None,
Launch,
Toast,
AppNotification,
Protocol,
}

View File

@@ -1,16 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Windows.AppNotifications;
namespace Snap.Hutao.Core.LifeCycle;
internal interface IAppActivation
{
void Activate(HutaoActivationArguments args);
void PostInitialization();
}
void NotificationInvoked(AppNotificationManager manager, AppNotificationActivatedEventArgs args);
internal interface IAppActivationActionHandlersAccess
{
ValueTask HandleLaunchGameActionAsync(string? uid = null);
void PostInitialization();
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.LifeCycle;
internal interface IAppActivationActionHandlersAccess
{
ValueTask HandleLaunchGameActionAsync(string? uid = null);
}

View File

@@ -13,6 +13,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
{
private readonly PrivateNamedPipeMessageDispatcher messageDispatcher;
private readonly RuntimeOptions runtimeOptions;
private readonly ILogger<PrivateNamedPipeServer> logger;
private readonly CancellationTokenSource serverTokenSource = new();
private readonly SemaphoreSlim serverSemaphore = new(1);
@@ -23,6 +24,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
{
messageDispatcher = serviceProvider.GetRequiredService<PrivateNamedPipeMessageDispatcher>();
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
logger = serviceProvider.GetRequiredService<ILogger<PrivateNamedPipeServer>>();
PipeSecurity? pipeSecurity = default;
@@ -64,6 +66,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
try
{
await serverStream.WaitForConnectionAsync(serverTokenSource.Token).ConfigureAwait(false);
logger.LogInformation("Pipe session created");
RunPacketSession(serverStream, serverTokenSource.Token);
}
catch (OperationCanceledException)
@@ -78,6 +81,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
while (serverStream.IsConnected && !token.IsCancellationRequested)
{
serverStream.ReadPacket(out PipePacketHeader header);
logger.LogInformation("Pipe packet: [Type:{Type}] [Command:{Command}]", header.Type, header.Command);
switch ((header.Type, header.Command))
{
case (PipePacketType.Request, PipePacketCommand.RequestElevationStatus):
@@ -87,6 +91,11 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
break;
case (PipePacketType.Request, PipePacketCommand.RedirectActivation):
HutaoActivationArguments? hutaoArgs = serverStream.ReadJsonContent<HutaoActivationArguments>(in header);
if (hutaoArgs is not null)
{
logger.LogInformation("Redirect activation: [Kind:{Kind}] [Arguments:{Arguments}]", hutaoArgs.Kind, hutaoArgs.LaunchActivatedArguments);
}
messageDispatcher.RedirectActivation(hutaoArgs);
break;
case (PipePacketType.SessionTermination, _):

View File

@@ -25,7 +25,7 @@ internal class AsyncBarrier
/// <param name="participants">The number of participants.</param>
public AsyncBarrier(int participants)
{
ArgumentOutOfRangeException.ThrowIfLessThan(participants, 1, "Participants of AsyncBarrier can not be less than 1");
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(participants, "Participants of AsyncBarrier must be greater than 0");
participantCount = participants;
// Allocate the stack so no resizing is necessary.

View File

@@ -4,11 +4,11 @@
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using Windows.Storage;
using static Snap.Hutao.Win32.ConstValues;
namespace Snap.Hutao.Core.Windowing.NotifyIcon;
@@ -26,9 +26,10 @@ internal sealed class NotifyIconController : IDisposable
{
lazyMenu = new(() => new(serviceProvider));
StorageFile iconFile = StorageFile.GetFileFromApplicationUriAsync("ms-appx:///Assets/Logo.ico".ToUri()).AsTask().GetAwaiter().GetResult();
icon = new(iconFile.Path);
id = Unsafe.As<byte, Guid>(ref MemoryMarshal.GetArrayDataReference(MD5.HashData(Encoding.UTF8.GetBytes(iconFile.Path))));
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
string iconPath = Path.Combine(runtimeOptions.InstalledLocation, "Assets/Logo.ico");
icon = new(iconPath);
id = Unsafe.As<byte, Guid>(ref MemoryMarshal.GetArrayDataReference(MD5.HashData(Encoding.UTF8.GetBytes(iconPath))));
xamlHostWindow = new(serviceProvider);
xamlHostWindow.MoveAndResize(default);

View File

@@ -31,6 +31,12 @@ internal static class WindowExtension
return WindowControllers.TryGetValue(window, out _);
}
public static void UninitializeController<TWindow>(this TWindow window)
where TWindow : Window
{
WindowControllers.Remove(window);
}
public static DesktopWindowXamlSource? GetDesktopWindowXamlSource(this Window window)
{
if (window.SystemBackdrop is SystemBackdropDesktopWindowXamlSourceAccess access)

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Notifications;
using Microsoft.UI;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Content;
@@ -9,6 +8,7 @@ using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.Windows.AppNotifications.Builder;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Windowing.Abstraction;
@@ -99,14 +99,13 @@ internal sealed class XamlWindowController
private void OnWindowClosed(object sender, WindowEventArgs args)
{
serviceProvider.GetRequiredService<AppOptions>().PropertyChanged -= OnOptionsPropertyChanged;
if (XamlLifetime.ApplicationLaunchedWithNotifyIcon && !XamlLifetime.ApplicationExiting)
{
args.Handled = true;
window.Hide();
if (!IsNotifyIconVisible())
{
new ToastContentBuilder()
new AppNotificationBuilder()
.AddText(SH.CoreWindowingNotifyIconPromotedHint)
.Show();
}
@@ -119,16 +118,15 @@ internal sealed class XamlWindowController
GC.Collect(GC.MaxGeneration);
}
else
{
if (window is IXamlWindowRectPersisted rectPersisted)
{
SaveOrSkipWindowSize(rectPersisted);
}
subclass?.Dispose();
windowNonRudeHWND?.Dispose();
if (window is IXamlWindowRectPersisted rectPersisted)
{
SaveOrSkipWindowSize(rectPersisted);
}
subclass?.Dispose();
windowNonRudeHWND?.Dispose();
window.UninitializeController();
}
private bool IsNotifyIconVisible()

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppNotifications.Builder;
namespace Snap.Hutao.Extension;
internal static class AppNotificationBuilderExtension
{
/// <summary>
/// Build and show the notification
/// </summary>
/// <param name="builder">this</param>
/// <param name="manager">Defaults to <see cref="AppNotificationManager.Default"/></param>
public static void Show(this AppNotificationBuilder builder, AppNotificationManager? manager = default)
{
(manager ?? AppNotificationManager.Default).Show(builder.BuildNotification());
}
}

View File

@@ -4,6 +4,7 @@
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Service;
using System.Collections.Concurrent;
namespace Snap.Hutao.Factory.ContentDialog;
@@ -18,10 +19,27 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
private readonly ConcurrentQueue<Func<Task>> dialogQueue = [];
private bool isDialogShowing;
public bool IsDialogShowing
{
get
{
if (currentWindowReference.Window is not { } window)
{
return false;
}
return isDialogShowing;
}
}
/// <inheritdoc/>
public async ValueTask<ContentDialogResult> CreateForConfirmAsync(string title, string content)
{
await taskContext.SwitchToMainThreadAsync();
Microsoft.UI.Xaml.Controls.ContentDialog dialog = new()
{
XamlRoot = currentWindowReference.GetXamlRoot(),
@@ -39,6 +57,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
public async ValueTask<ContentDialogResult> CreateForConfirmCancelAsync(string title, string content, ContentDialogButton defaultButton = ContentDialogButton.Close)
{
await taskContext.SwitchToMainThreadAsync();
Microsoft.UI.Xaml.Controls.ContentDialog dialog = new()
{
XamlRoot = currentWindowReference.GetXamlRoot(),
@@ -57,6 +76,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
public async ValueTask<Microsoft.UI.Xaml.Controls.ContentDialog> CreateForIndeterminateProgressAsync(string title)
{
await taskContext.SwitchToMainThreadAsync();
Microsoft.UI.Xaml.Controls.ContentDialog dialog = new()
{
XamlRoot = currentWindowReference.GetXamlRoot(),
@@ -72,9 +92,11 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
where TContentDialog : Microsoft.UI.Xaml.Controls.ContentDialog
{
await taskContext.SwitchToMainThreadAsync();
TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters);
contentDialog.XamlRoot = currentWindowReference.GetXamlRoot();
contentDialog.RequestedTheme = appOptions.ElementTheme;
return contentDialog;
}
@@ -84,6 +106,51 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters);
contentDialog.XamlRoot = currentWindowReference.GetXamlRoot();
contentDialog.RequestedTheme = appOptions.ElementTheme;
return contentDialog;
}
[SuppressMessage("", "SH003")]
public Task<ContentDialogResult> EnqueueAndShowAsync(Microsoft.UI.Xaml.Controls.ContentDialog contentDialog)
{
TaskCompletionSource<ContentDialogResult> dialogShowCompletionSource = new();
dialogQueue.Enqueue(async () =>
{
try
{
ContentDialogResult result = await contentDialog.ShowAsync();
dialogShowCompletionSource.SetResult(result);
}
catch (Exception ex)
{
dialogShowCompletionSource.SetException(ex);
}
finally
{
ShowNextDialog().SafeForget();
}
});
if (!isDialogShowing)
{
ShowNextDialog();
}
return dialogShowCompletionSource.Task;
Task ShowNextDialog()
{
if (dialogQueue.TryDequeue(out Func<Task>? showNextDialogAsync))
{
isDialogShowing = true;
return showNextDialogAsync();
}
else
{
isDialogShowing = false;
return Task.CompletedTask;
}
}
}
}

View File

@@ -11,6 +11,8 @@ namespace Snap.Hutao.Factory.ContentDialog;
[HighQuality]
internal interface IContentDialogFactory
{
bool IsDialogShowing { get; }
/// <summary>
/// 异步确认
/// </summary>
@@ -40,4 +42,6 @@ internal interface IContentDialogFactory
ValueTask<TContentDialog> CreateInstanceAsync<TContentDialog>(params object[] parameters)
where TContentDialog : Microsoft.UI.Xaml.Controls.ContentDialog;
Task<ContentDialogResult> EnqueueAndShowAsync(Microsoft.UI.Xaml.Controls.ContentDialog contentDialog);
}

View File

@@ -13,7 +13,7 @@ using Windows.Graphics;
namespace Snap.Hutao;
[HighQuality]
[Injection(InjectAs.Singleton)]
[Injection(InjectAs.Transient)]
internal sealed partial class LaunchGameWindow : Window,
IDisposable,
IXamlWindowExtendContentIntoTitleBar,

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao;
/// 主窗体
/// </summary>
[HighQuality]
[Injection(InjectAs.Singleton)]
[Injection(InjectAs.Transient)]
internal sealed partial class MainWindow : Window,
IXamlWindowExtendContentIntoTitleBar,
IXamlWindowRectPersisted,

View File

@@ -0,0 +1,654 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Snap.Hutao.Model.Entity.Database;
#nullable disable
namespace Snap.Hutao.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20240616104646_UidProfilePicture")]
partial class UidProfilePicture
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid>("ArchiveId")
.HasColumnType("TEXT");
b.Property<uint>("Current")
.HasColumnType("INTEGER");
b.Property<uint>("Id")
.HasColumnType("INTEGER");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("Time")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ArchiveId");
b.ToTable("achievements");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("achievement_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CalculatorRefreshTime")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("GameRecordRefreshTime")
.HasColumnType("TEXT");
b.Property<string>("Info")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("ShowcaseRefreshTime")
.HasColumnType("TEXT");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("avatar_infos");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("Id")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("cultivate_entries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("AvatarLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("AvatarLevelTo")
.HasColumnType("INTEGER");
b.Property<Guid>("EntryId")
.HasColumnType("TEXT");
b.Property<uint>("SkillALevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillALevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("SkillELevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillELevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("SkillQLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillQLevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("WeaponLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("WeaponLevelTo")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("EntryId")
.IsUnique();
b.ToTable("cultivate_entry_level_informations");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("Count")
.HasColumnType("INTEGER");
b.Property<Guid>("EntryId")
.HasColumnType("TEXT");
b.Property<bool>("IsFinished")
.HasColumnType("INTEGER");
b.Property<uint>("ItemId")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("EntryId");
b.ToTable("cultivate_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AttachedUid")
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("cultivate_projects");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("DailyNote")
.HasColumnType("TEXT");
b.Property<bool>("DailyTaskNotify")
.HasColumnType("INTEGER");
b.Property<bool>("DailyTaskNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<bool>("ExpeditionNotify")
.HasColumnType("INTEGER");
b.Property<bool>("ExpeditionNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<bool>("HomeCoinNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<int>("HomeCoinNotifyThreshold")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("RefreshTime")
.HasColumnType("TEXT");
b.Property<bool>("ResinNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<int>("ResinNotifyThreshold")
.HasColumnType("INTEGER");
b.Property<bool>("TransformerNotify")
.HasColumnType("INTEGER");
b.Property<bool>("TransformerNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("UserId");
b.ToTable("daily_notes");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("gacha_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid>("ArchiveId")
.HasColumnType("TEXT");
b.Property<int>("GachaType")
.HasColumnType("INTEGER");
b.Property<long>("Id")
.HasColumnType("INTEGER");
b.Property<uint>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("QueryType")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("Time")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ArchiveId");
b.ToTable("gacha_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AttachUid")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<string>("MihoyoSDK")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.ToTable("game_accounts");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("Count")
.HasColumnType("INTEGER");
b.Property<uint>("ItemId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AppendPropIdList")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<int>("MainPropId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_reliquaries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.Property<int>("PromoteLevel")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_weapons");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("ExpireTime")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Key");
b.ToTable("object_cache");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Key");
b.ToTable("settings");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("ScheduleId")
.HasColumnType("INTEGER");
b.Property<string>("SpiralAbyss")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("spiral_abysses");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.UidProfilePicture", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("AvatarId")
.HasColumnType("INTEGER");
b.Property<uint>("CostumeId")
.HasColumnType("INTEGER");
b.Property<uint>("ProfilePictureId")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("RefreshTime")
.HasColumnType("TEXT");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("uid_profile_pictures");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Aid")
.HasColumnType("TEXT");
b.Property<string>("CookieToken")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CookieTokenLastUpdateTime")
.HasColumnType("TEXT");
b.Property<string>("Fingerprint")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<bool>("IsOversea")
.HasColumnType("INTEGER");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("LToken")
.HasColumnType("TEXT")
.HasColumnName("Ltoken");
b.Property<string>("Mid")
.HasColumnType("TEXT");
b.Property<string>("PreferredUid")
.HasColumnType("TEXT");
b.Property<string>("SToken")
.HasColumnType("TEXT")
.HasColumnName("Stoken");
b.HasKey("InnerId");
b.ToTable("users");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
.WithMany()
.HasForeignKey("ArchiveId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Archive");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
.WithOne("LevelInformation")
.HasForeignKey("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", "EntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Entry");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
.WithMany()
.HasForeignKey("EntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Entry");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
.WithMany()
.HasForeignKey("ArchiveId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Archive");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.Navigation("LevelInformation");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,39 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Snap.Hutao.Migrations
{
/// <inheritdoc />
public partial class UidProfilePicture : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "uid_profile_pictures",
columns: table => new
{
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
Uid = table.Column<string>(type: "TEXT", nullable: false),
ProfilePictureId = table.Column<uint>(type: "INTEGER", nullable: false),
AvatarId = table.Column<uint>(type: "INTEGER", nullable: false),
CostumeId = table.Column<uint>(type: "INTEGER", nullable: false),
RefreshTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_uid_profile_pictures", x => x.InnerId);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "uid_profile_pictures");
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
@@ -466,6 +466,33 @@ namespace Snap.Hutao.Migrations
b.ToTable("spiral_abysses");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.UidProfilePicture", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("AvatarId")
.HasColumnType("INTEGER");
b.Property<uint>("CostumeId")
.HasColumnType("INTEGER");
b.Property<uint>("ProfilePictureId")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("RefreshTime")
.HasColumnType("TEXT");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("uid_profile_pictures");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
{
b.Property<Guid>("InnerId")

View File

@@ -65,6 +65,8 @@ internal sealed class AppDbContext : DbContext
public DbSet<SpiralAbyssEntry> SpiralAbysses { get; set; } = default!;
public DbSet<UidProfilePicture> UidProfilePictures { get; set; } = default!;
public static AppDbContext Create(IServiceProvider serviceProvider, string sqlConnectionString)
{
DbContextOptions<AppDbContext> options = new DbContextOptionsBuilder<AppDbContext>()

View File

@@ -0,0 +1,41 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Web.Enka.Model;
using Snap.Hutao.Web.Hoyolab;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Snap.Hutao.Model.Entity;
[Table("uid_profile_pictures")]
internal sealed class UidProfilePicture : IMappingFrom<UidProfilePicture, PlayerUid, ProfilePicture>
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid InnerId { get; set; }
public string Uid { get; set; } = default!;
public uint ProfilePictureId { get; set; }
public uint AvatarId { get; set; }
public uint CostumeId { get; set; }
public DateTimeOffset RefreshTime { get; set; }
[SuppressMessage("", "SH002")]
public static UidProfilePicture From(PlayerUid uid, ProfilePicture profilePicture)
{
return new()
{
Uid = uid.ToString(),
ProfilePictureId = profilePicture.Id,
AvatarId = profilePicture.AvatarId,
CostumeId = profilePicture.CostumeId,
RefreshTime = DateTimeOffset.Now,
};
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
internal enum ProfilePictureUnlockType
{
None,
Item,
Avatar,
Costume,
ParentQuest,
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Avatar;
@@ -12,4 +13,17 @@ internal sealed class ProfilePicture
public string Icon { get; set; } = default!;
public string Name { get; set; } = default!;
}
public ProfilePictureUnlockType UnlockType { get; set; }
/// <summary>
/// <see cref="ProfilePictureUnlockType.Item"/> -> <see cref="MaterialId"/>
/// <br/>
/// <see cref="ProfilePictureUnlockType.Avatar"/> -> <see cref="AvatarId"/>
/// <br/>
/// <see cref="ProfilePictureUnlockType.Costume"/> -> <see cref="CostumeId"/>
/// <br/>
/// <see cref="ProfilePictureUnlockType.ParentQuest"/> -> <see cref="QuestId"/>
/// </summary>
public uint UnlockParameter { get; set; }
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Control;
namespace Snap.Hutao.Model.Metadata.Converter;
/// <summary>
/// 玩家头像转换器
/// </summary>
[HighQuality]
internal sealed class AvatarIconCircleConverter : ValueConverter<string, Uri>
{
/// <summary>
/// 名称转Uri
/// </summary>
/// <param name="name">名称</param>
/// <returns>链接</returns>
public static Uri IconNameToUri(string name)
{
return Web.HutaoEndpoints.StaticRaw("AvatarIconCircle", $"{name}.png").ToUri();
}
/// <inheritdoc/>
public override Uri Convert(string from)
{
return IconNameToUri(from);
}
}

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutao"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.10.3.0" />
Version="1.10.4.0" />
<Properties>
<DisplayName>Snap Hutao</DisplayName>
@@ -50,7 +50,7 @@
</desktop:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="Snap.Hutao.exe" Arguments="-ToastActivated" DisplayName="Snap Hutao Toast Activator">
<com:ExeServer Executable="Snap.Hutao.exe" Arguments="----AppNotificationActivated:" DisplayName="Snap Hutao Toast Activator">
<com:Class Id="5760EC4D-F7E8-4666-A965-9886D7DFFE7D" DisplayName="Snap Hutao Toast Activator"/>
</com:ExeServer>
</com:ComServer>

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutaoDev"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.10.3.0" />
Version="1.10.4.0" />
<Properties>
<DisplayName>Snap Hutao Dev</DisplayName>
@@ -50,7 +50,7 @@
</desktop:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="Snap.Hutao.exe" Arguments="-ToastActivated" DisplayName="Snap Hutao Dev Toast Activator">
<com:ExeServer Executable="Snap.Hutao.exe" Arguments="----AppNotificationActivated:" DisplayName="Snap Hutao Toast Activator">
<com:Class Id="F32B561D-752E-472B-A22C-85824D421E1A" DisplayName="Snap Hutao Dev Toast Activator"/>
</com:ExeServer>
</com:ComServer>

View File

@@ -193,16 +193,16 @@
<value>Register [{0}] hotkey [{1}] failed</value>
</data>
<data name="CoreWindowingNotifyIconExitLabel" xml:space="preserve">
<value>退出</value>
<value>Quit</value>
</data>
<data name="CoreWindowingNotifyIconLaunchGameLabel" xml:space="preserve">
<value>Launch</value>
</data>
<data name="CoreWindowingNotifyIconPromotedHint" xml:space="preserve">
<value>胡桃已进入后台运行</value>
<value>Snap Hutao is running in the background</value>
</data>
<data name="CoreWindowingNotifyIconViewLabel" xml:space="preserve">
<value>窗口</value>
<value>Window</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>Dark</value>
@@ -552,7 +552,7 @@
<value>Refinement {0}</value>
</data>
<data name="MustSelectUserAndUid" xml:space="preserve">
<value>Must sign in to your MiHoYo BBS account and select a user</value>
<value>Must sign in to MiYouShe/HoYoLAB and select a user and role</value>
</data>
<data name="ServerGachaLogServiceDeleteEntrySucceed" xml:space="preserve">
<value>Deleted {1} wish records from UID: {0}</value>
@@ -570,7 +570,7 @@
<value>Found abnormal data, unable to upload to Snap Hutao Cloud. Please do not upload across accounts or you can attempt to delete cloud data and try again.</value>
</data>
<data name="ServerGachaLogServiceUploadEntrySucceed" xml:space="preserve">
<value>Uploaded {1} wish records of UID: {0}, stored {2}</value>
<value>Uploaded {1} wish records to UID: {0}, {2} records saved</value>
</data>
<data name="ServerPassportLoginRequired" xml:space="preserve">
<value>Please login or register Snap Hutao account first</value>
@@ -621,7 +621,7 @@
<value>Verification request is too frequent. Please try again in 1 minute.</value>
</data>
<data name="ServerRecordBannedUid" xml:space="preserve">
<value>Failed to upload Sprial Abyss record, current UID is banned by Hutao Database</value>
<value>Upload Sprial Abyss record failed, current UID is banned by Hutao Database</value>
</data>
<data name="ServerRecordComputingStatistics" xml:space="preserve">
<value>Failed to upload Sprial Abyss record, server is calculating statistical data</value>
@@ -642,7 +642,7 @@
<value>Failed to upload Spiral Abyss record. It is not data for the current schedule.</value>
</data>
<data name="ServerRecordPreviousRequestNotCompleted" xml:space="preserve">
<value>Failed to upload Sprial Abyss record. The record for the current UID is still being processed. Please do not repeat the operation.</value>
<value>Upload Sprial Abyss record failed. The record for the current UID is still being processed. Please do not repeat the operation.</value>
</data>
<data name="ServerRecordUploadSuccessAndGachaLogServiceTimeExtended" xml:space="preserve">
<value>Uploaded Spiral Abyss record successfully. Received a privilege extension for Snap Hutao Cloud service.</value>
@@ -1034,6 +1034,9 @@
<data name="ServiceGameUnlockerReadProcessMemoryPointerAddressFailed" xml:space="preserve">
<value>Error reading process modules' memory: could not read valid value in given address</value>
</data>
<data name="ServiceGameUnlockerWriteProcessMemoryFpsAddressFailed" xml:space="preserve">
<value>Failed to unlock frame ratex{0:X8}</value>
</data>
<data name="ServiceHutaoUserGachaLogExpiredAt" xml:space="preserve">
<value>Wish history data backup service will expire at \n{0:yyyy.MM.dd HH:mm:ss}</value>
</data>
@@ -1373,13 +1376,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>Delete user data permanently?</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<value>Log in Now</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>No Hutao Passport logged in currently, uploading Abyss Records will not grant you Hutao Cloud privilege extension.</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>Log in Now</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>Continue to Upload</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">
@@ -1568,11 +1571,14 @@
<data name="ViewModelCultivationProjectInvalidName" xml:space="preserve">
<value>Can't add plan with invalid name</value>
</data>
<data name="ViewModelCultivationRefreshInventoryProgress" xml:space="preserve">
<value>Syncing inventory items</value>
</data>
<data name="ViewModelCultivationRemoveProjectContent" xml:space="preserve">
<value>此操作不可逆,此计划的养成物品与背包材料将会丢失</value>
<value>This action is irreversible, items and materials in this plan will lose</value>
</data>
<data name="ViewModelCultivationRemoveProjectTitle" xml:space="preserve">
<value>确认要删除当前计划吗?</value>
<value>Are you sure to delete current plan?</value>
</data>
<data name="ViewModelDailyNoteConfigWebhookUrlComplete" xml:space="preserve">
<value>Realtime Note Webhook URL successfully configured</value>
@@ -1925,6 +1931,9 @@
<data name="ViewPageCultivationNavigateAction" xml:space="preserve">
<value>Go</value>
</data>
<data name="ViewPageCultivationRefreshInventory" xml:space="preserve">
<value>Sync Inventory Items</value>
</data>
<data name="ViewPageCultivationRemoveEntry" xml:space="preserve">
<value>Delete list</value>
</data>
@@ -1965,7 +1974,7 @@
<value>Notification Settings</value>
</data>
<data name="ViewPageDailyNoteNotificationUnavailableHint" xml:space="preserve">
<value>胡桃的通知权限已被关闭</value>
<value>Snap Hutao notification permission has been disabled</value>
</data>
<data name="ViewPageDailyNoteRefresh" xml:space="preserve">
<value>Refresh</value>
@@ -1998,7 +2007,7 @@
<value>Refresh</value>
</data>
<data name="ViewPageDailyNoteSettingRefreshNotifyIconDisabledHint" xml:space="preserve">
<value>未启用通知区域图标,关闭窗口后自动刷新将不会执行</value>
<value>Notification area incon is disabled, auto-refresh will be be executed after closing the window</value>
</data>
<data name="ViewPageDailyNoteSlientModeDescription" xml:space="preserve">
<value>Do not disturb during Genshin gaming</value>
@@ -2067,10 +2076,10 @@
<value>Go to Afdian to Buy Related Services</value>
</data>
<data name="ViewPageGachaLogHutaoCloudAfdianPurchaseHeader" xml:space="preserve">
<value>Buy or Renew Cloud Service</value>
<value>Buy / Renew Cloud Services</value>
</data>
<data name="ViewPageGachaLogHutaoCloudDelete" xml:space="preserve">
<value>Delete the Cloud Archive of This UID</value>
<value>Delete cloud archive of this UID</value>
</data>
<data name="ViewPageGachaLogHutaoCloudDeveloperHint" xml:space="preserve">
<value>Lifetime developer license</value>
@@ -2079,7 +2088,7 @@
<value>Snap Hutao Cloud Service Expired</value>
</data>
<data name="ViewPageGachaLogHutaoCloudRetrieve" xml:space="preserve">
<value>Download the Cloud Archive of this UID</value>
<value>Download cloud archive of this UID</value>
</data>
<data name="ViewPageGachaLogHutaoCloudSpiralAbyssActivityDescription" xml:space="preserve">
<value>Free 3-day license after uploading current schedule Abyss record</value>
@@ -2406,7 +2415,7 @@
<value>Rename</value>
</data>
<data name="ViewPageLaunchGameSwitchSchemeDescription" xml:space="preserve">
<value>Switch game server (CN/BiliBili/Global)</value>
<value>Switch game server (CN/Bilibili/Global)</value>
</data>
<data name="ViewPageLaunchGameSwitchSchemeHeader" xml:space="preserve">
<value>Server</value>
@@ -2415,7 +2424,7 @@
<value>You need to convert to a server that matches the launcher before updating the version</value>
</data>
<data name="ViewPageLaunchGameUnlockFpsDescription" xml:space="preserve">
<value>Please turn off V-Sync in the game settings. You may need a high-performance graphic card to support a high frame rate limit.</value>
<value>Please turn off [V-Sync] in your game graphic settings, need high performance GPU to support higher refresh rate gaming</value>
</data>
<data name="ViewPageLaunchGameUnlockFpsHeader" xml:space="preserve">
<value>Unlock Frame Rate Limit</value>
@@ -2433,7 +2442,7 @@
<value>Windows HDR</value>
</data>
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
<value>Enter your HoYoLab UID</value>
<value>Please input your HoYoLAB UID</value>
</data>
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
<value>You are using an embedded webview to login to your MiHoYo passport account, the client will fetch the Cookie data when you click on I'm Signed in button. All network communication initialed in this webview occurs only between your computer and MiHoYo official servers.</value>
@@ -2511,7 +2520,7 @@
<value>Unless the developer explicitly asks you to do this, you should not execute the operations below!</value>
</data>
<data name="ViewPageSettingDataFolderDescription" xml:space="preserve">
<value>User data and metadata are saved here</value>
<value>User data/metadata are saved here</value>
</data>
<data name="ViewPageSettingDataFolderHeader" xml:space="preserve">
<value>Data Folder</value>
@@ -2589,7 +2598,7 @@
<value>Select the game server for which you want to get announcements</value>
</data>
<data name="ViewPageSettingHomeAnnouncementRegionHeader" xml:space="preserve">
<value>Announcement Server</value>
<value>Announcement Game Server Source</value>
</data>
<data name="ViewpageSettingHomeCardDescription" xml:space="preserve">
<value>Manage cards on home dashboard</value>
@@ -2646,7 +2655,7 @@
<value>You are unlimited in any testing feature</value>
</data>
<data name="ViewPageSettingHutaoPassportMaintainerHeader" xml:space="preserve">
<value>Snap Hutao developer and maintainer</value>
<value>Snap Hutao Developer / Maintainer</value>
</data>
<data name="ViewPageSettingHutaoPassportRedeemCodeDescription" xml:space="preserve">
<value>We sometimes give away Snap Hutao Cloud redemption codes to some users</value>
@@ -2679,10 +2688,10 @@
<value>Shortcut Keys</value>
</data>
<data name="ViewPageSettingNotifyIconDescription" xml:space="preserve">
<value>在通知区域显示图标,以允许执行后台任务,重启后生效</value>
<value>Show icon in notification area, to allow background task, restart the program to activate</value>
</data>
<data name="ViewPageSettingNotifyIconHeader" xml:space="preserve">
<value>通知区域图标</value>
<value>Notification Area Icon</value>
</data>
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
<value>Official Website</value>
@@ -2703,7 +2712,7 @@
<value>Reset Image Resource</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>Add Unlock Frame Rate Limit Option in Game Launcher Process Section</value>
<value>Add [Unlock Frame Rate Limit] in [Game Launcher - Process Linkage]</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>Game Launcher - Unlock Frame Rate Limit</value>
@@ -2751,7 +2760,7 @@
<value>Contribute Translations</value>
</data>
<data name="ViewPageSettingUnobtainedWishItemVisibleDescription" xml:space="preserve">
<value>Display undrawn wish items in Character and Weapon tabs in Wish Export</value>
<value>在「祈愿记录-角色」与「祈愿记录-武器」中显示未抽取到的祈愿物品</value>
</data>
<data name="ViewPageSettingUnobtainedWishItemVisibleHeader" xml:space="preserve">
<value>Undrawn Wish Items</value>
@@ -2997,7 +3006,7 @@
<value>Document</value>
</data>
<data name="ViewUserLoginMihoyoUserDisabledTooltip" xml:space="preserve">
<value>由于米游社安全策略的相关更改,网页登录暂不可用</value>
<value>Due to MiYouShe's security policy changes, login from web is unavailable</value>
</data>
<data name="ViewUserNoUserHint" xml:space="preserve">
<value>Haven't logged in</value>
@@ -3261,7 +3270,7 @@
<value>[{1}] network request exception in [{0}] please try again later</value>
</data>
<data name="WebResponseSignInErrorHint" xml:space="preserve">
<value>登录失败,请前往 HoYoLAB 初始化账号,原始消息:{0}</value>
<value>Login failed, please visit HoYoLAB to initial your account, original message: {0}</value>
</data>
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
<value>Monitor ID</value>

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -1373,13 +1373,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>是否永久删除用户数据</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>继续上传</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -1373,13 +1373,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>Hapus data pengguna secara permanen?</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>继续上传</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -1373,13 +1373,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>ユーザーデータを完全に削除しますか</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>继续上传</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -1373,13 +1373,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>사용자 데이터 영구 삭제</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>继续上传</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -1373,13 +1373,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>Excluir permanentemente os dados do usuário?</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>继续上传</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">

View File

@@ -1034,6 +1034,9 @@
<data name="ServiceGameUnlockerReadProcessMemoryPointerAddressFailed" xml:space="preserve">
<value>在读取游戏进程内存时遇到问题:无法读取到指定地址的有效值</value>
</data>
<data name="ServiceGameUnlockerWriteProcessMemoryFpsAddressFailed" xml:space="preserve">
<value>解锁帧率失败0x{0:X8}</value>
</data>
<data name="ServiceHutaoUserGachaLogExpiredAt" xml:space="preserve">
<value>祈愿记录上传服务有效期至\n{0:yyyy.MM.dd HH:mm:ss}</value>
</data>
@@ -1373,13 +1376,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>是否永久删除用户数据</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>继续上传</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -1373,13 +1373,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>是否永久删除用户数据</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>Продолжить загрузку</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -190,7 +190,7 @@
<value>Không phát hiện WebView2 Runtime</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value/>
<value />
</data>
<data name="CoreWindowingNotifyIconExitLabel" xml:space="preserve">
<value>Thoát</value>
@@ -292,7 +292,7 @@
<value>刷新于 {0:MM.dd HH:mm:ss}</value>
</data>
<data name="ModelEntitySpiralAbyssScheduleFormat" xml:space="preserve">
<value/>
<value />
</data>
<data name="ModelInterchangeUIGFItemTypeAvatar" xml:space="preserve">
<value>Tài khoản</value>
@@ -395,7 +395,7 @@
<value>4 Sao</value>
</data>
<data name="ModelIntrinsicItemQualityRed" xml:space="preserve">
<value/>
<value />
</data>
<data name="ModelIntrinsicItemQualityWhite" xml:space="preserve">
<value>1 sao</value>
@@ -669,7 +669,7 @@
<value>Đã tìm thấy nhiều ID thành tựu giống hệt nhau trong kho lưu trữ thành tựu</value>
</data>
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
<value/>
<value />
<comment>Need EXACT same string in game</comment>
</data>
<data name="ServiceAvatarInfoPropertyBaseAtk" xml:space="preserve">
@@ -1005,7 +1005,7 @@
<value>获取 Package Version 失败</value>
</data>
<data name="ServiceGamePackageRequestScatteredFileFailed" xml:space="preserve">
<value/>
<value />
</data>
<data name="ServiceGamePathLocateFailed" xml:space="preserve">
<value>无法找到游戏路径,请前往设置修改</value>
@@ -1373,13 +1373,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>是否永久删除用户数据</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>继续上传</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -1373,13 +1373,13 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>是否永久刪除用戶數據</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>前往登入畫面</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>當前未登入胡桃賬號,上傳深淵數據無法獲贈胡桃雲時長</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginSecondaryButtonText" xml:space="preserve">
<value>繼續上傳</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Notifications;
using Microsoft.Windows.AppNotifications;
using Snap.Hutao.Core;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Model.Entity;
@@ -20,7 +20,6 @@ namespace Snap.Hutao.Service.DailyNote;
[Injection(InjectAs.Singleton)]
internal sealed partial class DailyNoteNotificationOperation
{
private const string ToastHeaderIdArgument = "DAILYNOTE";
private const string ToastAttributionUnknown = "Unknown";
private readonly ITaskContext taskContext;
@@ -52,63 +51,57 @@ internal sealed partial class DailyNoteNotificationOperation
? entry.UserGameRole.ToString()
: await GetUserUidAsync(entry).ConfigureAwait(false);
ToastContentBuilder builder = new ToastContentBuilder()
.AddHeader(ToastHeaderIdArgument, SH.ServiceDailyNoteNotifierTitle, ToastHeaderIdArgument)
.AddAttributionText(attribution)
.AddButton(new ToastButton()
.SetContent(SH.ServiceDailyNoteNotifierActionLaunchGameButton)
.AddArgument(AppActivation.Action, AppActivation.LaunchGame)
.AddArgument(AppActivation.Uid, entry.Uid))
.AddButton(new ToastButtonDismiss(SH.ServiceDailyNoteNotifierActionLaunchGameDismiss));
if (options.IsReminderNotification)
{
builder.SetToastScenario(ToastScenario.Reminder);
}
string reminder = options.IsReminderNotification ? @"scenario=""reminder""" : string.Empty;
string content;
if (notifyInfos.Count > 2)
{
builder.AddText(SH.ServiceDailyNoteNotifierMultiValueReached);
string adaptiveSubgroups = string.Join(string.Empty, notifyInfos.Select(info => $"""
<subgroup>
<text hint-align="center">{info.AdaptiveHint}</text>
<text hint-style="captionSubtle" hint-align="center">{info.Title}</text>
</subgroup>
"""));
// Desktop and Mobile started supporting adaptive toasts in API contract 3 (Anniversary Update)
if (UniversalApiContract.IsPresent(WindowsVersion.Windows10AnniversaryUpdate))
{
AdaptiveGroup group = new();
foreach (DailyNoteNotifyInfo info in notifyInfos)
{
AdaptiveSubgroup subgroup = new()
{
HintWeight = 1,
Children =
{
// new AdaptiveImage() { Source = info.AdaptiveIcon, HintRemoveMargin = true, },
new AdaptiveText() { Text = info.AdaptiveHint, HintAlign = AdaptiveTextAlign.Center, },
new AdaptiveText() { Text = info.Title, HintAlign = AdaptiveTextAlign.Center, HintStyle = AdaptiveTextStyle.CaptionSubtle, },
},
};
group.Children.Add(subgroup);
}
builder.AddVisualChild(group);
}
content = $"""
<text>{SH.ServiceDailyNoteNotifierMultiValueReached}</text>
<group>
{adaptiveSubgroups}
</group>
""";
}
else
{
foreach (DailyNoteNotifyInfo info in notifyInfos)
{
builder.AddText(info.Hint);
}
content = string.Join(string.Empty, notifyInfos.Select(info => $"""
<text>{info.Hint}</text>
"""));
}
string rawXml = $"""
<toast {reminder}>
<header title="{SH.ServiceDailyNoteNotifierTitle}" id="DAILYNOTE" arguments="DAILYNOTE"/>
<visual>
<binding template="ToastGeneric">
{content}
<text placement="attribution">{attribution}</text>
</binding>
</visual>
<actions>
<action activationType="background" content="{SH.ServiceDailyNoteNotifierActionLaunchGameButton}" arguments="{AppActivation.Action}={AppActivation.LaunchGame};{AppActivation.Uid}={entry.Uid}"/>
<action activationType="system" content="{SH.ServiceDailyNoteNotifierActionLaunchGameDismiss}" arguments="dismiss"/>
</actions>
</toast>
""";
AppNotification notification = new(rawXml);
if (options.IsSilentWhenPlayingGame && gameService.IsGameRunning())
{
notification.SuppressDisplay = true;
}
await taskContext.SwitchToMainThreadAsync();
builder.Show(toast => toast.SuppressPopup = ShouldSuppressPopup(options));
}
private bool ShouldSuppressPopup(DailyNoteOptions options)
{
// Prevent notify when we are in game && silent mode.
return options.IsSilentWhenPlayingGame && gameService.IsGameRunning();
AppNotificationManager.Default.Show(notification);
}
private async ValueTask<string> GetUserUidAsync(DailyNoteEntry entry)

View File

@@ -3,9 +3,7 @@
using Snap.Hutao.Core;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Graphics.Direct3D11;
using Windows.Graphics.Capture;
using static Snap.Hutao.Win32.ConstValues;
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
@@ -13,20 +11,6 @@ namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
[Injection(InjectAs.Singleton, typeof(IGameScreenCaptureService))]
internal sealed partial class GameScreenCaptureService : IGameScreenCaptureService
{
private const uint CreateDXGIFactoryFlag =
#if DEBUG
DXGI_CREATE_FACTORY_DEBUG;
#else
0;
#endif
private const D3D11_CREATE_DEVICE_FLAG D3d11CreateDeviceFlag =
D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_BGRA_SUPPORT
#if DEBUG
| D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_DEBUG
#endif
;
private readonly ILogger<GameScreenCaptureService> logger;
public bool IsSupported()

View File

@@ -10,8 +10,10 @@ using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Service.Game.Configuration;
using Snap.Hutao.Service.Game.Package;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Resource;
using Snap.Hutao.Web.Hoyolab.HoyoPlay.Connect;
using Snap.Hutao.Web.Hoyolab.HoyoPlay.Connect.ChannelSDK;
using Snap.Hutao.Web.Hoyolab.HoyoPlay.Connect.DeprecatedFile;
using Snap.Hutao.Web.Hoyolab.HoyoPlay.Connect.Package;
using Snap.Hutao.Web.Response;
using System.IO;
@@ -43,7 +45,7 @@ internal sealed class LaunchExecutionEnsureGameResourceHandler : ILaunchExecutio
return;
}
// Backup config file, in order to prevent a incompatible launcher to delete it.
// Backup config file, recover when a incompatible launcher deleted it.
context.ServiceProvider.GetRequiredService<IGameConfigurationFileService>().Backup(gameFileSystem.GameConfigFilePath);
await context.TaskContext.SwitchToMainThreadAsync();
@@ -96,13 +98,30 @@ internal sealed class LaunchExecutionEnsureGameResourceHandler : ILaunchExecutio
progress.Report(new(SH.ServiceGameEnsureGameResourceQueryResourceInformation));
ResourceClient resourceClient = context.ServiceProvider.GetRequiredService<ResourceClient>();
Response<GameResource> response = await resourceClient.GetResourceAsync(context.Scheme).ConfigureAwait(false);
HoyoPlayClient hoyoPlayClient = context.ServiceProvider.GetRequiredService<HoyoPlayClient>();
if (!response.TryGetDataWithoutUINotification(out GameResource? resource))
// We perform these requests before package conversion to ensure resources index is intact.
Response<GamePackagesWrapper> packagesResponse = await hoyoPlayClient.GetPackagesAsync(context.Scheme).ConfigureAwait(false);
if (!packagesResponse.TryGetDataWithoutUINotification(out GamePackagesWrapper? gamePackages))
{
context.Result.Kind = LaunchExecutionResultKind.GameResourceIndexQueryInvalidResponse;
context.Result.ErrorMessage = SH.FormatServiceGameLaunchExecutionGameResourceQueryIndexFailed(response);
context.Result.ErrorMessage = SH.FormatServiceGameLaunchExecutionGameResourceQueryIndexFailed(packagesResponse);
return false;
}
Response<GameChannelSDKsWrapper> sdkResponse = await hoyoPlayClient.GetChannelSDKAsync(context.Scheme).ConfigureAwait(false);
if (!sdkResponse.TryGetDataWithoutUINotification(out GameChannelSDKsWrapper? channelSDKs))
{
context.Result.Kind = LaunchExecutionResultKind.GameResourceIndexQueryInvalidResponse;
context.Result.ErrorMessage = SH.FormatServiceGameLaunchExecutionGameResourceQueryIndexFailed(sdkResponse);
return false;
}
Response<DeprecatedFileConfigurationsWrapper> deprecatedFileResponse = await hoyoPlayClient.GetDeprecatedFileConfigurationsAsync(context.Scheme).ConfigureAwait(false);
if (!deprecatedFileResponse.TryGetDataWithoutUINotification(out DeprecatedFileConfigurationsWrapper? deprecatedFileConfigs))
{
context.Result.Kind = LaunchExecutionResultKind.GameResourceIndexQueryInvalidResponse;
context.Result.ErrorMessage = SH.FormatServiceGameLaunchExecutionGameResourceQueryIndexFailed(deprecatedFileResponse);
return false;
}
@@ -110,7 +129,7 @@ internal sealed class LaunchExecutionEnsureGameResourceHandler : ILaunchExecutio
if (!context.Scheme.ExecutableMatches(gameFileName))
{
if (!await packageConverter.EnsureGameResourceAsync(context.Scheme, resource, gameFolder, progress).ConfigureAwait(false))
if (!await packageConverter.EnsureGameResourceAsync(context.Scheme, gamePackages.GamePackages.Single(), gameFolder, progress).ConfigureAwait(false))
{
context.Result.Kind = LaunchExecutionResultKind.GameResourcePackageConvertInternalError;
context.Result.ErrorMessage = SH.ViewModelLaunchGameEnsureGameResourceFail;
@@ -124,7 +143,7 @@ internal sealed class LaunchExecutionEnsureGameResourceHandler : ILaunchExecutio
context.Options.UpdateGamePathAndRefreshEntries(Path.Combine(gameFolder, executableName));
}
await packageConverter.EnsureDeprecatedFilesAndSdkAsync(resource, gameFolder).ConfigureAwait(false);
await packageConverter.EnsureDeprecatedFilesAndSdkAsync(channelSDKs.GameChannelSDKs.SingleOrDefault(), deprecatedFileConfigs.DeprecatedFileConfigurations.SingleOrDefault(), gameFolder).ConfigureAwait(false);
return true;
}

View File

@@ -20,7 +20,7 @@ internal sealed class LaunchExecutionSetChannelOptionsHandler : ILaunchExecution
string configPath = gameFileSystem.GameConfigFilePath;
context.Logger.LogInformation("Game config file path: {ConfigPath}", configPath);
List<IniElement> elements = default!;
List<IniElement> elements;
try
{
elements = [.. IniSerializer.DeserializeFromFile(configPath)];

View File

@@ -13,7 +13,6 @@ internal sealed class PackageConvertStatus
public PackageConvertStatus(string name)
{
Name = name;
Description = name;
}
public PackageConvertStatus(string name, long bytesRead, long totalBytes)

View File

@@ -8,7 +8,9 @@ using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.Hashing;
using Snap.Hutao.Core.IO.Http.Sharding;
using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Resource;
using Snap.Hutao.Web.Hoyolab.HoyoPlay.Connect.ChannelSDK;
using Snap.Hutao.Web.Hoyolab.HoyoPlay.Connect.DeprecatedFile;
using Snap.Hutao.Web.Hoyolab.HoyoPlay.Connect.Package;
using System.Globalization;
using System.IO;
using System.IO.Compression;
@@ -34,9 +36,9 @@ internal sealed partial class PackageConverter
private readonly HttpClient httpClient;
private readonly ILogger<PackageConverter> logger;
public async ValueTask<bool> EnsureGameResourceAsync(LaunchScheme targetScheme, GameResource gameResource, string gameFolder, IProgress<PackageConvertStatus> progress)
public async ValueTask<bool> EnsureGameResourceAsync(LaunchScheme targetScheme, GamePackage gamePackage, string gameFolder, IProgress<PackageConvertStatus> progress)
{
// 以 国服 => 国际 为例
// 以 国服 -> 国际 为例
// 1. 下载国际服的 pkg_version 文件,转换为索引字典
// 获取本地对应 pkg_version 文件,转换为索引字典
//
@@ -56,7 +58,7 @@ internal sealed partial class PackageConverter
// 替换操作等于 先备份国服文件,随后新增国际服文件
// 准备下载链接
string scatteredFilesUrl = gameResource.Game.Latest.DecompressedPath;
string scatteredFilesUrl = gamePackage.Main.Major.ResourceListUrl;
string pkgVersionUrl = $"{scatteredFilesUrl}/{PackageVersion}";
PackageConverterFileSystemContext context = new(targetScheme.IsOversea, runtimeOptions.GetDataFolderServerCacheFolder(), gameFolder, scatteredFilesUrl);
@@ -77,7 +79,7 @@ internal sealed partial class PackageConverter
return await ReplaceGameResourceAsync(diffOperations, context, progress).ConfigureAwait(false);
}
public async ValueTask EnsureDeprecatedFilesAndSdkAsync(GameResource resource, string gameFolder)
public async ValueTask EnsureDeprecatedFilesAndSdkAsync(GameChannelSDK? channelSDK, DeprecatedFilesWrapper? deprecatedFiles, string gameFolder)
{
string sdkDllBackup = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll.backup");
string sdkDll = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll");
@@ -86,9 +88,9 @@ internal sealed partial class PackageConverter
string sdkVersion = Path.Combine(gameFolder, "sdk_pkg_version");
// Only bilibili's sdk is not null
if (resource.Sdk is not null)
if (channelSDK is not null)
{
using (Stream sdkWebStream = await httpClient.GetStreamAsync(resource.Sdk.Path).ConfigureAwait(false))
using (Stream sdkWebStream = await httpClient.GetStreamAsync(channelSDK.ChannelSdkPackage.Url).ConfigureAwait(false))
{
ZipFile.ExtractToDirectory(sdkWebStream, gameFolder, true);
}
@@ -106,9 +108,9 @@ internal sealed partial class PackageConverter
FileOperation.Move(sdkVersion, sdkVersionBackup, true);
}
if (resource.DeprecatedFiles is not null)
if (deprecatedFiles is not null)
{
foreach (NameMd5 file in resource.DeprecatedFiles)
foreach (DeprecatedFile file in deprecatedFiles.DeprecatedFiles)
{
string filePath = Path.Combine(gameFolder, file.Name);
FileOperation.Move(filePath, $"{filePath}.backup", true);

View File

@@ -30,25 +30,13 @@ internal class LaunchScheme : IEquatable<ChannelOptions>
}
}
/// <summary>
/// 通道
/// </summary>
public ChannelType Channel { get; private protected set; }
/// <summary>
/// 子通道
/// </summary>
public SubChannelType SubChannel { get; private protected set; }
/// <summary>
/// 启动器 Id
/// </summary>
public int LauncherId { get; private protected set; }
public string LauncherId { get; private protected set; } = default!;
/// <summary>
/// API Key
/// </summary>
public string Key { get; private protected set; } = default!;
public string GameId { get; private protected set; } = default!;
/// <summary>
/// 是否为海外

View File

@@ -7,13 +7,13 @@ namespace Snap.Hutao.Service.Game.Scheme;
internal sealed class LaunchSchemeBilibili : LaunchScheme
{
private const int SdkStaticLauncherBilibiliId = 17;
private const string SdkStaticLauncherBilibiliKey = "KAtdSsoQ";
private const string HoyoPlayLauncherBilibiliId = "umfgRO5gh5";
private const string HoyoPlayGameBilibiliId = "T2S0Gz4Dr2";
public LaunchSchemeBilibili(SubChannelType subChannel, bool isNotCompatOnly = true)
{
LauncherId = SdkStaticLauncherBilibiliId;
Key = SdkStaticLauncherBilibiliKey;
LauncherId = HoyoPlayLauncherBilibiliId;
GameId = HoyoPlayGameBilibiliId;
Channel = ChannelType.Bili;
SubChannel = subChannel;
IsOversea = false;

View File

@@ -7,13 +7,13 @@ namespace Snap.Hutao.Service.Game.Scheme;
internal sealed class LaunchSchemeChinese : LaunchScheme
{
private const int SdkStaticLauncherChineseId = 18;
private const string SdkStaticLauncherChineseKey = "eYd89JmJ";
private const string HoyoPlayLauncherChineseId = "jGHBHlcOq1";
private const string HoyoPlayGameChineseId = "1Z8W5NHUQb";
public LaunchSchemeChinese(ChannelType channel, SubChannelType subChannel, bool isNotCompatOnly = true)
{
LauncherId = SdkStaticLauncherChineseId;
Key = SdkStaticLauncherChineseKey;
LauncherId = HoyoPlayLauncherChineseId;
GameId = HoyoPlayGameChineseId;
Channel = channel;
SubChannel = subChannel;
IsOversea = false;

View File

@@ -7,13 +7,13 @@ namespace Snap.Hutao.Service.Game.Scheme;
internal sealed class LaunchSchemeOversea : LaunchScheme
{
private const int SdkStaticLauncherOverseaId = 10;
private const string SdkStaticLauncherOverseaKey = "gcStgarh";
private const string HoyoPlayLauncherOverseaId = "VYTpXlbWo8";
private const string HoyoPlayGameOverseaId = "gopR6Cufr3";
public LaunchSchemeOversea(ChannelType channel, SubChannelType subChannel, bool isNotCompatOnly = true)
{
LauncherId = SdkStaticLauncherOverseaId;
Key = SdkStaticLauncherOverseaKey;
LauncherId = HoyoPlayLauncherOverseaId;
GameId = HoyoPlayGameOverseaId;
Channel = channel;
SubChannel = subChannel;
IsOversea = true;

View File

@@ -54,6 +54,12 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
if (!context.GameProcess.HasExited && context.FpsAddress != 0U)
{
UnsafeWriteProcessMemory(context.GameProcess, context.FpsAddress, launchOptions.TargetFps);
WIN32_ERROR error = GetLastError();
if (error is not WIN32_ERROR.NO_ERROR)
{
context.Description = SH.FormatServiceGameUnlockerWriteProcessMemoryFpsAddressFailed(error);
}
context.Report();
}
else

View File

@@ -24,51 +24,52 @@ internal sealed partial class HutaoSpiralAbyssService : IHutaoSpiralAbyssService
private readonly IMemoryCache memoryCache;
/// <inheritdoc/>
public ValueTask<Overview> GetOverviewAsync()
public ValueTask<Overview> GetOverviewAsync(bool last = false)
{
return FromCacheOrWebAsync(nameof(Overview), homaClient.GetOverviewAsync);
return FromCacheOrWebAsync(nameof(Overview), last, homaClient.GetOverviewAsync);
}
/// <inheritdoc/>
public ValueTask<List<AvatarAppearanceRank>> GetAvatarAppearanceRanksAsync()
public ValueTask<List<AvatarAppearanceRank>> GetAvatarAppearanceRanksAsync(bool last = false)
{
return FromCacheOrWebAsync(nameof(AvatarAppearanceRank), homaClient.GetAvatarAttendanceRatesAsync);
return FromCacheOrWebAsync(nameof(AvatarAppearanceRank), last, homaClient.GetAvatarAttendanceRatesAsync);
}
/// <inheritdoc/>
public ValueTask<List<AvatarUsageRank>> GetAvatarUsageRanksAsync()
public ValueTask<List<AvatarUsageRank>> GetAvatarUsageRanksAsync(bool last = false)
{
return FromCacheOrWebAsync(nameof(AvatarUsageRank), homaClient.GetAvatarUtilizationRatesAsync);
return FromCacheOrWebAsync(nameof(AvatarUsageRank), last, homaClient.GetAvatarUtilizationRatesAsync);
}
/// <inheritdoc/>
public ValueTask<List<AvatarConstellationInfo>> GetAvatarConstellationInfosAsync()
public ValueTask<List<AvatarConstellationInfo>> GetAvatarConstellationInfosAsync(bool last = false)
{
return FromCacheOrWebAsync(nameof(AvatarConstellationInfo), homaClient.GetAvatarHoldingRatesAsync);
return FromCacheOrWebAsync(nameof(AvatarConstellationInfo), last, homaClient.GetAvatarHoldingRatesAsync);
}
/// <inheritdoc/>
public ValueTask<List<AvatarCollocation>> GetAvatarCollocationsAsync()
public ValueTask<List<AvatarCollocation>> GetAvatarCollocationsAsync(bool last = false)
{
return FromCacheOrWebAsync(nameof(AvatarCollocation), homaClient.GetAvatarCollocationsAsync);
return FromCacheOrWebAsync(nameof(AvatarCollocation), last, homaClient.GetAvatarCollocationsAsync);
}
/// <inheritdoc/>
public ValueTask<List<WeaponCollocation>> GetWeaponCollocationsAsync()
public ValueTask<List<WeaponCollocation>> GetWeaponCollocationsAsync(bool last = false)
{
return FromCacheOrWebAsync(nameof(WeaponCollocation), homaClient.GetWeaponCollocationsAsync);
return FromCacheOrWebAsync(nameof(WeaponCollocation), last, homaClient.GetWeaponCollocationsAsync);
}
/// <inheritdoc/>
public ValueTask<List<TeamAppearance>> GetTeamAppearancesAsync()
public ValueTask<List<TeamAppearance>> GetTeamAppearancesAsync(bool last = false)
{
return FromCacheOrWebAsync(nameof(TeamAppearance), homaClient.GetTeamCombinationsAsync);
return FromCacheOrWebAsync(nameof(TeamAppearance), last, homaClient.GetTeamCombinationsAsync);
}
private async ValueTask<T> FromCacheOrWebAsync<T>(string typeName, Func<CancellationToken, ValueTask<HutaoResponse<T>>> taskFunc)
private async ValueTask<T> FromCacheOrWebAsync<T>(string typeName, bool last, Func<bool, CancellationToken, ValueTask<HutaoResponse<T>>> taskFunc)
where T : class, new()
{
string key = $"{nameof(HutaoSpiralAbyssService)}.Cache.{typeName}";
string kind = last ? "Last" : "Current";
string key = $"{nameof(HutaoSpiralAbyssService)}.Cache.{typeName}.{kind}";
if (memoryCache.TryGetValue(key, out object? cache))
{
T? t = cache as T;
@@ -81,7 +82,7 @@ internal sealed partial class HutaoSpiralAbyssService : IHutaoSpiralAbyssService
return memoryCache.Set(key, value, cacheExpireTime);
}
Response<T> webResponse = await taskFunc(default).ConfigureAwait(false);
Response<T> webResponse = await taskFunc(last, default).ConfigureAwait(false);
T? data = webResponse.Data;
if (data is not null)

View File

@@ -9,6 +9,7 @@ using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.ViewModel.Complex;
using Snap.Hutao.Web.Hutao.SpiralAbyss;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Service.Hutao;
@@ -124,6 +125,32 @@ internal sealed partial class HutaoSpiralAbyssStatisticsCache : IHutaoSpiralAbys
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IEnumerable<TResult> CurrentLeftJoinLast<TElement, TKey, TResult>(IEnumerable<TElement> current, IEnumerable<TElement>? last, Func<TElement, TKey> keySelector, Func<TElement, TElement?, TResult> resultSelector)
where TKey : notnull
{
if (last is null)
{
foreach (TElement element in current)
{
yield return resultSelector(element, default);
}
}
else
{
Dictionary<TKey, TElement> lastMap = [];
foreach (TElement element in last)
{
lastMap[keySelector(element)] = element;
}
foreach (TElement element in current)
{
yield return resultSelector(element, lastMap.GetValueOrDefault(keySelector(element)));
}
}
}
private async ValueTask<Dictionary<AvatarId, Avatar>> GetIdAvatarMapExtendedAsync()
{
if (idAvatarExtendedMap is null)
@@ -137,86 +164,91 @@ internal sealed partial class HutaoSpiralAbyssStatisticsCache : IHutaoSpiralAbys
private async ValueTask AvatarCollocationsAsync(Dictionary<AvatarId, Avatar> idAvatarMap, Dictionary<WeaponId, Weapon> idWeaponMap, Dictionary<ExtendedEquipAffixId, Model.Metadata.Reliquary.ReliquarySet> idReliquarySetMap)
{
List<AvatarCollocation> avatarCollocationsRaw;
List<AvatarCollocation> raw, rawLast;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IHutaoSpiralAbyssService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoSpiralAbyssService>();
avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false);
raw = await hutaoService.GetAvatarCollocationsAsync(false).ConfigureAwait(false);
rawLast = await hutaoService.GetAvatarCollocationsAsync(true).ConfigureAwait(false);
}
AvatarCollocations = avatarCollocationsRaw.SelectList(co => new AvatarCollocationView()
AvatarCollocations = CurrentLeftJoinLast(raw, rawLast, data => data.AvatarId, (raw, rawLast) => new AvatarCollocationView()
{
AvatarId = co.AvatarId,
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
Weapons = co.Weapons.SelectList(w => new WeaponView(idWeaponMap[w.Item], w.Rate)),
ReliquarySets = co.Reliquaries.SelectList(r => new ReliquarySetView(r, idReliquarySetMap)),
AvatarId = raw.AvatarId,
Avatars = CurrentLeftJoinLast(raw.Avatars, rawLast?.Avatars, data => data.Item, (avatar, avatarLast) => new AvatarView(idAvatarMap[avatar.Item], avatar.Rate, avatarLast?.Rate)).ToList(),
Weapons = CurrentLeftJoinLast(raw.Weapons, rawLast?.Weapons, data => data.Item, (weapon, weaponLast) => new WeaponView(idWeaponMap[weapon.Item], weapon.Rate, weaponLast?.Rate)).ToList(),
ReliquarySets = CurrentLeftJoinLast(raw.Reliquaries, rawLast?.Reliquaries, data => data.Item, (relic, relicLast) => new ReliquarySetView(idReliquarySetMap, relic, relicLast)).ToList(),
}).ToDictionary(a => a.AvatarId);
}
private async ValueTask WeaponCollocationsAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
{
List<WeaponCollocation> weaponCollocationsRaw;
List<WeaponCollocation> raw, rawLast;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IHutaoSpiralAbyssService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoSpiralAbyssService>();
weaponCollocationsRaw = await hutaoService.GetWeaponCollocationsAsync().ConfigureAwait(false);
raw = await hutaoService.GetWeaponCollocationsAsync(false).ConfigureAwait(false);
rawLast = await hutaoService.GetWeaponCollocationsAsync(true).ConfigureAwait(false);
}
WeaponCollocations = weaponCollocationsRaw.SelectList(co => new WeaponCollocationView()
WeaponCollocations = CurrentLeftJoinLast(raw, rawLast, data => data.WeaponId, (raw, rawLast) => new WeaponCollocationView()
{
WeaponId = co.WeaponId,
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
WeaponId = raw.WeaponId,
Avatars = CurrentLeftJoinLast(raw.Avatars, rawLast?.Avatars, data => data.Item, (avatar, avatarLast) => new AvatarView(idAvatarMap[avatar.Item], avatar.Rate, avatarLast?.Rate)).ToList(),
}).ToDictionary(w => w.WeaponId);
}
[SuppressMessage("", "SH003")]
private async Task AvatarAppearanceRankAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
{
List<AvatarAppearanceRank> avatarAppearanceRanksRaw;
List<AvatarAppearanceRank> raw, rawLast;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IHutaoSpiralAbyssService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoSpiralAbyssService>();
avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false);
raw = await hutaoService.GetAvatarAppearanceRanksAsync(false).ConfigureAwait(false);
rawLast = await hutaoService.GetAvatarAppearanceRanksAsync(true).ConfigureAwait(false);
}
AvatarAppearanceRanks = avatarAppearanceRanksRaw.SortByDescending(r => r.Floor).SelectList(rank => new AvatarRankView
AvatarAppearanceRanks = CurrentLeftJoinLast(raw.SortByDescending(r => r.Floor), rawLast, data => data.Floor, (raw, rawLast) => new AvatarRankView
{
Floor = SH.FormatModelBindingHutaoComplexRankFloor(rank.Floor),
Avatars = rank.Ranks.SortByDescending(r => r.Rate).SelectList(rank => new AvatarView(idAvatarMap[rank.Item], rank.Rate)),
});
Floor = SH.FormatModelBindingHutaoComplexRankFloor(raw.Floor),
Avatars = CurrentLeftJoinLast(raw.Ranks.SortByDescending(r => r.Rate), rawLast?.Ranks, data => data.Item, (rank, rankLast) => new AvatarView(idAvatarMap[rank.Item], rank.Rate, rankLast?.Rate)).ToList(),
}).ToList();
}
[SuppressMessage("", "SH003")]
private async Task AvatarUsageRanksAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
{
List<AvatarUsageRank> avatarUsageRanksRaw;
List<AvatarUsageRank> raw, rawLast;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IHutaoSpiralAbyssService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoSpiralAbyssService>();
avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false);
raw = await hutaoService.GetAvatarUsageRanksAsync(false).ConfigureAwait(false);
rawLast = await hutaoService.GetAvatarUsageRanksAsync(true).ConfigureAwait(false);
}
AvatarUsageRanks = avatarUsageRanksRaw.SortByDescending(r => r.Floor).SelectList(rank => new AvatarRankView
AvatarUsageRanks = CurrentLeftJoinLast(raw.SortByDescending(r => r.Floor), rawLast, data => data.Floor, (raw, rawLast) => new AvatarRankView
{
Floor = SH.FormatModelBindingHutaoComplexRankFloor(rank.Floor),
Avatars = rank.Ranks.SortByDescending(r => r.Rate).SelectList(rank => new AvatarView(idAvatarMap[rank.Item], rank.Rate)),
});
Floor = SH.FormatModelBindingHutaoComplexRankFloor(raw.Floor),
Avatars = CurrentLeftJoinLast(raw.Ranks.SortByDescending(r => r.Rate), rawLast?.Ranks, data => data.Item, (rank, rankLast) => new AvatarView(idAvatarMap[rank.Item], rank.Rate, rankLast?.Rate)).ToList(),
}).ToList();
}
[SuppressMessage("", "SH003")]
private async Task AvatarConstellationInfosAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
{
List<AvatarConstellationInfo> avatarConstellationInfosRaw;
List<AvatarConstellationInfo> raw, rawLast;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IHutaoSpiralAbyssService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoSpiralAbyssService>();
avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false);
raw = await hutaoService.GetAvatarConstellationInfosAsync(false).ConfigureAwait(false);
rawLast = await hutaoService.GetAvatarConstellationInfosAsync(true).ConfigureAwait(false);
}
AvatarConstellationInfos = avatarConstellationInfosRaw.SortBy(i => i.HoldingRate).SelectList(info =>
AvatarConstellationInfos = CurrentLeftJoinLast(raw.SortBy(i => i.HoldingRate), rawLast, data => data.AvatarId, (raw, rawLast) => new AvatarConstellationInfoView(idAvatarMap[raw.AvatarId], raw.HoldingRate, rawLast?.HoldingRate)
{
return new AvatarConstellationInfoView(idAvatarMap[info.AvatarId], info.HoldingRate, info.Constellations.SelectList(x => x.Rate));
});
Rates = CurrentLeftJoinLast(raw.Constellations, rawLast?.Constellations, data => data.Item, (rate, rataLast) => new RateAndDelta(rate.Rate, rataLast?.Rate)).ToList(),
}).ToList();
}
[SuppressMessage("", "SH003")]

View File

@@ -11,45 +11,17 @@ namespace Snap.Hutao.Service.Hutao;
[HighQuality]
internal interface IHutaoSpiralAbyssService
{
/// <summary>
/// 异步获取角色上场率
/// </summary>
/// <returns>角色上场率</returns>
ValueTask<List<AvatarAppearanceRank>> GetAvatarAppearanceRanksAsync();
ValueTask<List<AvatarAppearanceRank>> GetAvatarAppearanceRanksAsync(bool last = false);
/// <summary>
/// 异步获取角色搭配
/// </summary>
/// <returns>角色搭配</returns>
ValueTask<List<AvatarCollocation>> GetAvatarCollocationsAsync();
ValueTask<List<AvatarCollocation>> GetAvatarCollocationsAsync(bool last = false);
/// <summary>
/// 异步获取角色持有率信息
/// </summary>
/// <returns>角色持有率信息</returns>
ValueTask<List<AvatarConstellationInfo>> GetAvatarConstellationInfosAsync();
ValueTask<List<AvatarConstellationInfo>> GetAvatarConstellationInfosAsync(bool last = false);
/// <summary>
/// 异步获取角色使用率
/// </summary>
/// <returns>角色使用率</returns>
ValueTask<List<AvatarUsageRank>> GetAvatarUsageRanksAsync();
ValueTask<List<AvatarUsageRank>> GetAvatarUsageRanksAsync(bool last = false);
/// <summary>
/// 异步获取统计数据
/// </summary>
/// <returns>统计数据</returns>
ValueTask<Overview> GetOverviewAsync();
ValueTask<Overview> GetOverviewAsync(bool last = false);
/// <summary>
/// 异步获取队伍上场
/// </summary>
/// <returns>队伍上场</returns>
ValueTask<List<TeamAppearance>> GetTeamAppearancesAsync();
ValueTask<List<TeamAppearance>> GetTeamAppearancesAsync(bool last = false);
/// <summary>
/// 异步获取武器搭配
/// </summary>
/// <returns>武器搭配</returns>
ValueTask<List<WeaponCollocation>> GetWeaponCollocationsAsync();
ValueTask<List<WeaponCollocation>> GetWeaponCollocationsAsync(bool last = false);
}

View File

@@ -25,8 +25,8 @@ internal sealed partial class DailyNoteRefreshJobScheduler : IJobScheduler
ITrigger dailyNoteTrigger = TriggerBuilder.Create()
.WithIdentity(JobIdentity.DailyNoteRefreshTriggerName, JobIdentity.DailyNoteGroupName)
.StartNow()
.WithSimpleSchedule(builder => builder.WithIntervalInSeconds(interval).RepeatForever())
.StartAt(DateTimeOffset.Now.AddSeconds(interval))
.Build();
await scheduler.ScheduleJob(dailyNoteJob, dailyNoteTrigger).ConfigureAwait(false);

View File

@@ -7,5 +7,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdAchievementSource
{
public Dictionary<AchievementId, Model.Metadata.Achievement.Achievement> IdAchievementMap { get; set; }
Dictionary<AchievementId, Model.Metadata.Achievement.Achievement> IdAchievementMap { get; set; }
}

View File

@@ -7,5 +7,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdAvatarSource
{
public Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> IdAvatarMap { get; set; }
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> IdAvatarMap { get; set; }
}

View File

@@ -8,5 +8,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdMaterialSource
{
public Dictionary<MaterialId, Material> IdMaterialMap { get; set; }
Dictionary<MaterialId, Material> IdMaterialMap { get; set; }
}

View File

@@ -8,5 +8,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdReliquaryAffixWeightSource
{
public Dictionary<AvatarId, ReliquaryAffixWeight> IdReliquaryAffixWeightMap { get; set; }
Dictionary<AvatarId, ReliquaryAffixWeight> IdReliquaryAffixWeightMap { get; set; }
}

View File

@@ -8,5 +8,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdReliquaryMainPropertySource
{
public Dictionary<ReliquaryMainAffixId, FightProperty> IdReliquaryMainPropertyMap { get; set; }
Dictionary<ReliquaryMainAffixId, FightProperty> IdReliquaryMainPropertyMap { get; set; }
}

View File

@@ -8,5 +8,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdReliquarySource
{
public Dictionary<ReliquaryId, Reliquary> IdReliquaryMap { get; set; }
Dictionary<ReliquaryId, Reliquary> IdReliquaryMap { get; set; }
}

View File

@@ -8,5 +8,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdReliquarySubAffixSource
{
public Dictionary<ReliquarySubAffixId, ReliquarySubAffix> IdReliquarySubAffixMap { get; set; }
Dictionary<ReliquarySubAffixId, ReliquarySubAffix> IdReliquarySubAffixMap { get; set; }
}

View File

@@ -7,5 +7,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdWeaponSource
{
public Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> IdWeaponMap { get; set; }
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> IdWeaponMap { get; set; }
}

View File

@@ -5,5 +5,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryNameAvatarSource
{
public Dictionary<string, Model.Metadata.Avatar.Avatar> NameAvatarMap { get; set; }
Dictionary<string, Model.Metadata.Avatar.Avatar> NameAvatarMap { get; set; }
}

View File

@@ -5,5 +5,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryNameWeaponSource
{
public Dictionary<string, Model.Metadata.Weapon.Weapon> NameWeaponMap { get; set; }
Dictionary<string, Model.Metadata.Weapon.Weapon> NameWeaponMap { get; set; }
}

View File

@@ -5,5 +5,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataListAchievementSource
{
public List<Model.Metadata.Achievement.Achievement> Achievements { get; set; }
List<Model.Metadata.Achievement.Achievement> Achievements { get; set; }
}

View File

@@ -7,5 +7,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataListChapterSource
{
public List<Chapter> Chapters { get; set; }
List<Chapter> Chapters { get; set; }
}

View File

@@ -7,5 +7,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataListGachaEventSource
{
public List<GachaEvent> GachaEvents { get; set; }
List<GachaEvent> GachaEvents { get; set; }
}

View File

@@ -7,5 +7,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataListMaterialSource
{
public List<Material> Materials { get; set; }
List<Material> Materials { get; set; }
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Avatar;
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataListProfilePictureSource
{
List<ProfilePicture> ProfilePictures { get; set; }
}

View File

@@ -7,5 +7,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataListReliquaryMainAffixLevelSource
{
public List<ReliquaryMainAffixLevel> ReliquaryMainAffixLevels { get; set; }
List<ReliquaryMainAffixLevel> ReliquaryMainAffixLevels { get; set; }
}

View File

@@ -7,5 +7,5 @@ namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataListReliquarySource
{
public List<Reliquary> Reliquaries { get; set; }
List<Reliquary> Reliquaries { get; set; }
}

View File

@@ -38,6 +38,11 @@ internal static class MetadataServiceContextExtension
listMaterialSource.Materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false);
}
if (context is IMetadataListProfilePictureSource dictionaryIdProfilePictureSource)
{
dictionaryIdProfilePictureSource.ProfilePictures = await metadataService.GetProfilePictureListAsync(token).ConfigureAwait(false);
}
if (context is IMetadataListReliquaryMainAffixLevelSource listReliquaryMainAffixLevelSource)
{
listReliquaryMainAffixLevelSource.ReliquaryMainAffixLevels = await metadataService.GetReliquaryMainAffixLevelListAsync(token).ConfigureAwait(false);

View File

@@ -16,6 +16,7 @@ internal static class MetadataFileNames
public const string FileNameMaterial = "Material";
public const string FileNameMonster = "Monster";
public const string FileNameMonsterCurve = "MonsterCurve";
public const string FileNameProfilePicture = "ProfilePicture";
public const string FileNameReliquary = "Reliquary";
public const string FileNameReliquaryAffixWeight = "ReliquaryAffixWeight";
public const string FileNameReliquaryMainAffix = "ReliquaryMainAffix";

View File

@@ -70,6 +70,11 @@ internal static class MetadataServiceListExtension
return metadataService.FromCacheOrFileAsync<List<GrowCurve>>(FileNameMonsterCurve, token);
}
public static ValueTask<List<ProfilePicture>> GetProfilePictureListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<ProfilePicture>>(FileNameProfilePicture, token);
}
public static ValueTask<List<Reliquary>> GetReliquaryListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<Reliquary>>(FileNameReliquary, token);

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Notifications;
using Microsoft.Windows.AppNotifications;
namespace Snap.Hutao.Service.Notification;
@@ -16,7 +16,8 @@ internal sealed class ToastNotificationLifeTime : IToastNotificationLifeTime
// 用于在程序退出时尝试清除所有的系统通知
try
{
ToastNotificationManagerCompat.History.Clear();
AppNotificationManager.Default.RemoveAllAsync().AsTask().GetAwaiter().GetResult();
AppNotificationManager.Default.Unregister();
}
catch
{

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
namespace Snap.Hutao.Service.User;
internal interface IProfilePictureService
{
ValueTask TryInitializeAsync(ViewModel.User.User user, CancellationToken token = default(CancellationToken));
ValueTask RefreshUserGameRoleAsync(UserGameRole userGameRole, CancellationToken token = default(CancellationToken));
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
namespace Snap.Hutao.Service.User;
internal interface IUidProfilePictureDbService : IAppDbService<UidProfilePicture>
{
ValueTask<UidProfilePicture?> SingleUidProfilePictureOrDefaultByUidAsync(string uid, CancellationToken token = default);
ValueTask UpdateUidProfilePictureAsync(UidProfilePicture profilePicture, CancellationToken token = default);
ValueTask DeleteUidProfilePictureByUidAsync(string uid, CancellationToken token = default);
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Service.Metadata.ContextAbstraction;
namespace Snap.Hutao.Service.User;
internal interface IUserMetadataContext : IMetadataContext,
IMetadataListProfilePictureSource;

View File

@@ -53,4 +53,6 @@ internal interface IUserService
/// <param name="user">待移除的用户</param>
/// <returns>任务</returns>
ValueTask RemoveUserAsync(BindingUser user);
ValueTask RefreshProfilePictureAsync(UserGameRole userGameRole);
}

View File

@@ -0,0 +1,109 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.Web.Enka;
using Snap.Hutao.Web.Enka.Model;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
namespace Snap.Hutao.Service.User;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IProfilePictureService))]
internal sealed partial class ProfilePictureService : IProfilePictureService
{
private readonly IUidProfilePictureDbService uidProfilePictureDbService;
private readonly IMetadataService metadataService;
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
public async ValueTask TryInitializeAsync(ViewModel.User.User user, CancellationToken token = default)
{
foreach (UserGameRole userGameRole in user.UserGameRoles)
{
if (await uidProfilePictureDbService.SingleUidProfilePictureOrDefaultByUidAsync(userGameRole.GameUid, token).ConfigureAwait(false) is { } profilePicture)
{
if (await TryUpdateUserGameRoleAsync(userGameRole, profilePicture, token).ConfigureAwait(false))
{
continue;
}
}
// Force update
await RefreshUserGameRoleAsync(userGameRole, token: token).ConfigureAwait(false);
}
}
public async ValueTask RefreshUserGameRoleAsync(UserGameRole userGameRole, CancellationToken token = default)
{
EnkaResponse? enkaResponse;
using (IServiceScope scope = serviceProvider.CreateScope())
{
EnkaClient enkaClient = scope.ServiceProvider
.GetRequiredService<EnkaClient>();
enkaResponse = await enkaClient.GetForwardPlayerInfoAsync(userGameRole, token).ConfigureAwait(false)
?? await enkaClient.GetPlayerInfoAsync(userGameRole, token).ConfigureAwait(false);
}
if (enkaResponse is { PlayerInfo: { } playerInfo })
{
UidProfilePicture profilePicture = UidProfilePicture.From(userGameRole, playerInfo.ProfilePicture);
await uidProfilePictureDbService.DeleteUidProfilePictureByUidAsync(userGameRole.GameUid, token).ConfigureAwait(false);
await uidProfilePictureDbService.UpdateUidProfilePictureAsync(profilePicture, token).ConfigureAwait(false);
await TryUpdateUserGameRoleAsync(userGameRole, profilePicture, token).ConfigureAwait(false);
}
}
private async ValueTask<bool> TryUpdateUserGameRoleAsync(UserGameRole userGameRole, UidProfilePicture cache, CancellationToken token = default)
{
if (cache.RefreshTime.AddDays(15) < DateTimeOffset.Now)
{
return false;
}
if (!await metadataService.InitializeAsync().ConfigureAwait(false))
{
return false;
}
UserMetadataContext context = await metadataService.GetContextAsync<UserMetadataContext>(token).ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
// Most common to most rare
if (cache.ProfilePictureId is not 0U)
{
userGameRole.ProfilePictureIcon = context.ProfilePictures
.Single(p => p.Id == cache.ProfilePictureId)
.Icon;
return true;
}
if (cache.AvatarId is not 0U)
{
userGameRole.ProfilePictureIcon = context.ProfilePictures
.Single(p => p.UnlockType is ProfilePictureUnlockType.Avatar && p.UnlockParameter == cache.AvatarId)
.Icon;
return true;
}
if (cache.CostumeId is not 0U)
{
userGameRole.ProfilePictureIcon = context.ProfilePictures
.Single(p => p.UnlockType is ProfilePictureUnlockType.Costume && p.UnlockParameter == cache.CostumeId)
.Icon;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
namespace Snap.Hutao.Service.User;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IUidProfilePictureDbService))]
internal sealed partial class UidProfilePictureDbService : IUidProfilePictureDbService
{
private readonly IServiceProvider serviceProvider;
public IServiceProvider ServiceProvider { get => serviceProvider; }
public ValueTask<UidProfilePicture?> SingleUidProfilePictureOrDefaultByUidAsync(string uid, CancellationToken token = default)
{
return this.QueryAsync(query => query.SingleOrDefaultAsync(n => n.Uid == uid));
}
public async ValueTask UpdateUidProfilePictureAsync(UidProfilePicture profilePicture, CancellationToken token = default)
{
await this.UpdateAsync(profilePicture, token).ConfigureAwait(false);
}
public async ValueTask DeleteUidProfilePictureByUidAsync(string uid, CancellationToken token = default)
{
await this.DeleteAsync(profilePicture => profilePicture.Uid == uid, token).ConfigureAwait(false);
}
}

View File

@@ -16,6 +16,7 @@ namespace Snap.Hutao.Service.User;
internal sealed partial class UserInitializationService : IUserInitializationService
{
private readonly IUserFingerprintService userFingerprintService;
private readonly IProfilePictureService profilePictureService;
private readonly IServiceProvider serviceProvider;
public async ValueTask<ViewModel.User.User> ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default)
@@ -90,6 +91,7 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
}
await userFingerprintService.TryInitializeAsync(user, token).ConfigureAwait(false);
await profilePictureService.TryInitializeAsync(user, token).ConfigureAwait(false);
return user.IsInitialized = true;
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Avatar;
namespace Snap.Hutao.Service.User;
internal class UserMetadataContext : IUserMetadataContext
{
public List<ProfilePicture> ProfilePictures { get; set; } = default!;
}

View File

@@ -21,6 +21,7 @@ namespace Snap.Hutao.Service.User;
[Injection(InjectAs.Singleton, typeof(IUserService))]
internal sealed partial class UserService : IUserService, IUserServiceUnsafe
{
private readonly IProfilePictureService profilePictureService;
private readonly IUserCollectionService userCollectionService;
private readonly IServiceProvider serviceProvider;
private readonly IUserDbService userDbService;
@@ -121,4 +122,9 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
return true;
}
public async ValueTask RefreshProfilePictureAsync(UserGameRole userGameRole)
{
await profilePictureService.RefreshUserGameRoleAsync(userGameRole).ConfigureAwait(false);
}
}

View File

@@ -160,6 +160,7 @@
<None Remove="View\Control\LaunchGameResourceExpander.xaml" />
<None Remove="View\Control\LoadingView.xaml" />
<None Remove="View\Control\LoadingViewSlim.xaml" />
<None Remove="View\Control\RateDeltaTextBlockStyle.xaml" />
<None Remove="View\Control\SkillPivot.xaml" />
<None Remove="View\Control\SegmentedOverride.xaml" />
<None Remove="View\Control\StatisticsCard.xaml" />
@@ -312,10 +313,9 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="Google.OrTools" Version="9.10.4067" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -330,7 +330,7 @@
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240428000" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240607001" />
<PackageReference Include="QRCoder" Version="1.5.1" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.9.0" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
@@ -363,6 +363,11 @@
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<Page Update="View\Control\RateDeltaTextBlockStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Service\Game\Automation\ScreenCapture\GameScreenCaptureDebugPreviewWindow.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -16,6 +16,7 @@
d:DataContext="{d:DesignInstance shva:AchievementViewModelSlim}"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding NavigateCommand}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">

View File

@@ -18,6 +18,7 @@
d:DataContext="{d:DesignInstance shvd:DailyNoteViewModelSlim}"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding NavigateCommand}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">

View File

@@ -17,6 +17,7 @@
d:DataContext="{d:DesignInstance shvg:GachaLogViewModelSlim}"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding NavigateCommand}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">

View File

@@ -18,6 +18,7 @@
d:DataContext="{d:DesignInstance shvg:LaunchGameViewModelSlim}"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding LaunchCommand}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">

View File

@@ -6,12 +6,14 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shwhshlr="using:Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Resource"
xmlns:shwhhpc="using:Snap.Hutao.Web.Hoyolab.HoyoPlay.Connect"
xmlns:shwhhpcp="using:Snap.Hutao.Web.Hoyolab.HoyoPlay.Connect.Package"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
d:DataContext="{d:DesignInstance shwhshlr:Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Resource.Package}"
d:DataContext="{d:DesignInstance shwhhpcp:Package}"
BorderThickness="0"
IsExpanded="True"
ItemsSource="{Binding VoicePacks}"
ItemsSource="{Binding AllPackages}"
mc:Ignorable="d">
<cwc:SettingsExpander.Resources>
@@ -24,7 +26,7 @@
</cwc:SettingsExpander.Resources>
<cwc:SettingsExpander.ItemTemplate>
<DataTemplate x:DataType="shwhshlr:VoicePackage">
<DataTemplate x:DataType="shwhhpc:PackageSegment">
<cwc:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
ActionIcon="{shcm:FontIcon Glyph={StaticResource FontIconContentCopy}}"
@@ -35,70 +37,26 @@
<StackPanel
Grid.Row="1"
Margin="0,4,0,0"
Orientation="Horizontal">
Orientation="Horizontal"
Spacing="8">
<FontIcon FontSize="{StaticResource CaptionTextBlockFontSize}" Glyph="{StaticResource FontIconContentZipFolder}"/>
<TextBlock
Width="80"
Margin="8,0,0,0"
HorizontalAlignment="Left"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding PackageSize, Converter={StaticResource FileSizeToFriendlyStringConverter}}"/>
Text="{Binding Size, Converter={StaticResource FileSizeToFriendlyStringConverter}}"/>
<FontIcon FontSize="{StaticResource CaptionTextBlockFontSize}" Glyph="{StaticResource FontIconContentFolder}"/>
<TextBlock
Width="80"
Margin="8,0,0,0"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Size, Converter={StaticResource FileSizeToFriendlyStringConverter}}"/>
Text="{Binding DecompressedSize, Converter={StaticResource FileSizeToFriendlyStringConverter}}"/>
<FontIcon FontSize="{StaticResource CaptionTextBlockFontSize}" Glyph="{StaticResource FontIconContentAsteriskBadge12}"/>
<TextBlock
Margin="8,0,0,0"
IsTextSelectionEnabled="True"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Md5}"/>
Text="{Binding MD5}"/>
</StackPanel>
</cwc:SettingsCard.Description>
</cwc:SettingsCard>
</DataTemplate>
</cwc:SettingsExpander.ItemTemplate>
<cwc:SettingsExpander.ItemsHeader>
<cwc:SettingsCard
MinHeight="52"
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
ActionIcon="{shcm:FontIcon Glyph={StaticResource FontIconContentCopy}}"
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}"
BorderThickness="0"
Command="{Binding CopyPathCommand}"
CornerRadius="0"
Header="{Binding DisplayName}"
IsClickEnabled="True">
<cwc:SettingsCard.Description>
<StackPanel
Grid.Row="1"
Margin="0,4,0,0"
Orientation="Horizontal">
<FontIcon FontSize="{StaticResource CaptionTextBlockFontSize}" Glyph="{StaticResource FontIconContentZipFolder}"/>
<TextBlock
Width="80"
Margin="8,0,0,0"
HorizontalAlignment="Left"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding PackageSize, Converter={StaticResource FileSizeToFriendlyStringConverter}}"/>
<FontIcon FontSize="{StaticResource CaptionTextBlockFontSize}" Glyph="{StaticResource FontIconContentFolder}"/>
<TextBlock
Width="80"
Margin="8,0,0,0"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Size, Converter={StaticResource FileSizeToFriendlyStringConverter}}"/>
<FontIcon FontSize="{StaticResource CaptionTextBlockFontSize}" Glyph="{StaticResource FontIconContentAsteriskBadge12}"/>
<TextBlock
Margin="8,0,0,0"
IsTextSelectionEnabled="True"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Md5}"/>
</StackPanel>
</cwc:SettingsCard.Description>
</cwc:SettingsCard>
</cwc:SettingsExpander.ItemsHeader>
</cwc:SettingsExpander>

View File

@@ -0,0 +1,49 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.View.Control;
[TemplateVisualState(Name = "PositiveValue", GroupName = "CommonStates")]
[TemplateVisualState(Name = "NegativeValue", GroupName = "CommonStates")]
[DependencyProperty("Text", typeof(string), default(string), nameof(OnTextPropertyChanged))]
[DependencyProperty("TextStyle", typeof(Style))]
internal sealed partial class RateDeltaTextBlock : ContentControl
{
public RateDeltaTextBlock()
{
DefaultStyleKey = typeof(RateDeltaTextBlock);
DefaultStyleResourceUri = "ms-appx:///View/Control/RateDeltaTextBlockStyle.xaml".ToUri();
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
UpdateState();
}
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RateDeltaTextBlock control = (RateDeltaTextBlock)d;
control.UpdateState();
}
private void UpdateState()
{
if (Text is string { Length: > 0 } text)
{
_ = text.AsSpan()[0] switch
{
'+' => VisualStateManager.GoToState(this, "PositiveValue", true),
'-' => VisualStateManager.GoToState(this, "NegativeValue", true),
_ => VisualStateManager.GoToState(this, "NoValue", true),
};
}
else
{
VisualStateManager.GoToState(this, "NoValue", true);
}
}
}

Some files were not shown because too many files have changed in this diff Show More