Compare commits

..

49 Commits

Author SHA1 Message Date
Lightczx
0c093851ed code style 2024-02-23 09:33:55 +08:00
qhy040404
04c187aa16 real time refresh background 2024-02-22 22:34:12 +08:00
Lightczx
25cf9f5356 code style 2024-02-22 10:26:43 +08:00
DismissedLight
c30c8b114a re-enable backdrop 2024-02-21 23:38:57 +08:00
DismissedLight
41f5a04e5a Merge branch 'main' into develop 2024-02-21 20:35:38 +08:00
DismissedLight
4f26d6efc2 update package version 2024-02-21 20:33:18 +08:00
Masterain
0d6b5e595e New Crowdin updates (#1413) 2024-02-21 20:29:41 +08:00
DismissedLight
e30bebd1a3 Update StatisticsCard.xaml 2024-02-21 20:25:56 +08:00
Masterain
ffb34c0e93 New Crowdin updates (#1411) 2024-02-21 20:25:12 +08:00
Lightczx
35b35ab649 minor fix 5 2024-02-21 16:29:30 +08:00
Lightczx
f067350cd8 minor fix 2024-02-21 15:28:05 +08:00
Lightczx
14648dafe8 adjust language order 2024-02-21 14:04:24 +08:00
Lightczx
7a7d81cfee add static resource hint for more situation 2024-02-21 13:55:07 +08:00
Lightczx
30c055e7ba impl #1373 #1389 2024-02-21 11:57:41 +08:00
qhy040404
6a922d9db6 更新 alpha.yml 2024-02-21 09:52:20 +08:00
DismissedLight
02a6e64a8c Merge pull request #1408 from DGP-Studio/fix/launch 2024-02-20 14:40:57 +08:00
qhy040404
51c4e66472 reorder launch pipeline 2024-02-20 14:37:29 +08:00
Lightczx
48875195bf add wallpaper api 2024-02-20 14:25:48 +08:00
qhy040404
a179e0e838 Update build.cake (#1405) 2024-02-20 11:54:15 +08:00
qhy040404
00c3e94e97 Update alpha.yml 2024-02-20 11:27:14 +08:00
qhy040404
eec7224c07 Update alpha.yml 2024-02-20 11:27:13 +08:00
qhy040404
85bcf37b1b Update alpha.yml 2024-02-20 10:43:27 +08:00
qhy040404
465c7035c0 Update alpha.yml 2024-02-20 10:31:23 +08:00
DismissedLight
11620816ec Merge pull request #1403 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-a995a009f5 2024-02-20 09:07:19 +08:00
dependabot[bot]
c5b75d6f82 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [MSTest.TestAdapter](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.2.0 to 3.2.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v.3.2.0...v3.2.1)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 09:59:22 +00:00
qhy040404
6241bbc997 Setup dependabot for actions 2024-02-19 17:57:17 +08:00
qhy040404
afbd8ec6ad Revert "Update alpha.yml"
This reverts commit 288cc841ac.
2024-02-19 17:41:32 +08:00
qhy040404
288cc841ac Update alpha.yml 2024-02-19 17:26:00 +08:00
Lightczx
a9295c0a37 impl #1388 2024-02-19 16:58:55 +08:00
DismissedLight
29cd690032 Merge pull request #1402 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-f23264728b 2024-02-19 15:45:00 +08:00
dependabot[bot]
78d8539ae2 Bump the packages group in /src/Snap.Hutao with 2 updates
Bumps the packages group in /src/Snap.Hutao with 2 updates: [MSTest.TestAdapter](https://github.com/microsoft/testfx) and [MSTest.TestFramework](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.2.0 to 3.2.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v.3.2.0...v3.2.1)

Updates `MSTest.TestFramework` from 3.2.0 to 3.2.1
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v.3.2.0...v3.2.1)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 07:44:34 +00:00
Lightczx
7dec87586b fix #1233 2024-02-19 11:33:27 +08:00
Lightczx
d169f355f3 impl #1233 2024-02-19 10:33:25 +08:00
qhy040404
e1784e2078 Update issue templates 2024-02-18 21:16:44 +08:00
Lightczx
32b1b698df update logging message 2024-02-18 17:30:24 +08:00
Lightczx
d6c7df1593 update gacha statistics card style 2024-02-18 15:30:05 +08:00
Lightczx
904fdf7fc9 fix #1391 2024-02-18 09:43:51 +08:00
Lightczx
b3e4ebb5d3 bump nuget package version 2024-02-18 09:33:11 +08:00
DismissedLight
09b9af4575 Merge pull request #1401 from DGP-Studio/fix/1400
fix #1400
2024-02-18 09:18:48 +08:00
Lightczx
8930548f44 code style 2024-02-18 09:19:09 +08:00
DismissedLight
34dbcc6f5d Merge pull request #1392 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-dd5dd83d2f 2024-02-18 09:00:11 +08:00
qhy040404
75c25cec53 show warning if discord is not available 2024-02-17 14:00:23 +08:00
qhy040404
22251ca937 fix #1400 2024-02-17 11:28:36 +08:00
qhy040404
57c9531db8 fix #1365 again 2024-02-16 22:19:55 +08:00
dependabot[bot]
fd73743159 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest).


Updates `Microsoft.NET.Test.Sdk` from 17.8.0 to 17.9.0
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.8.0...v17.9.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 07:47:05 +00:00
qhy040404
6594d9032d fix wrong setting state 2024-02-12 11:29:25 +08:00
DismissedLight
ae1b452697 minor fix 2024-02-09 15:40:48 +08:00
DismissedLight
bc3df782e4 Merge pull request #1380 from DGP-Studio/fix/1379
fix #1379
2024-02-09 14:16:39 +08:00
qhy040404
635b52980b Update .gitlab-ci.yml 2024-02-07 21:13:59 +08:00
103 changed files with 2152 additions and 543 deletions

View File

@@ -18,7 +18,7 @@ body:
options:
- label: 我已阅读 Snap Hutao 文档中的[常见问题](https://hut.ao/advanced/FAQ.html)和[常见程序异常](https://hut.ao/advanced/exceptions.html),我的问题没有在文档中得到解答
required: true
- label: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
required: true
@@ -29,12 +29,12 @@ body:
id: winver
attributes:
label: Windows 版本
description: |
description: |
`Win+R` 输入 `winver` 回车后在打开的窗口第二行可以找到
placeholder: 22000.556
validations:
required: true
- type: input
id: shver
attributes:
@@ -48,10 +48,10 @@ body:
id: deviceid
attributes:
label: 设备 ID
description: |
description: |
在胡桃工具箱的反馈中心界面,你可以找到并复制你的设备 ID
如果你的问题涉及程序崩溃,请填写该项,这将有助于我们定位问题
如果你的程序已经无法启动,请下载并运行[诊断工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将显示你的设备 ID
如果你的程序已经无法启动,请下载并运行[诊断工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.Diagnostic.Tooling.exe),它将显示你的设备 ID
validations:
required: false
@@ -79,7 +79,7 @@ body:
- 公告
- 其它
validations:
required: true
required: true
- type: textarea
id: what-happened
@@ -107,4 +107,3 @@ body:
options:
- label: 我认为上述的描述已经足以详细,以允许开发人员能复现该问题
required: true

View File

@@ -18,7 +18,7 @@ body:
options:
- label: I have read [FAQ page](https://hut.ao/advanced/FAQ.html) and [Exception page](https://hut.ao/advanced/exceptions.html) in Snap Hutao document, and my issue is not answered
required: true
- label: I and tried **search feature** in Snap Hutao document site, and no associated article
required: true
@@ -29,12 +29,12 @@ body:
id: winver
attributes:
label: Windows Version
description: |
description: |
Use `Win+R` and input `winver`, Windows build version is usually at the second line
placeholder: e.g. 22000.556
validations:
required: true
- type: input
id: shver
attributes:
@@ -48,10 +48,10 @@ body:
id: deviceid
attributes:
label: Device ID
description: |
description: |
In Snap Hutao's Feedback Center, you can find and copy your device ID
If your issue is about program crash, please fill this so we can dump the log and locate the source easier
If your program cannot startup, please download and run [Diagnosis Tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will shows your device ID.
If your program cannot startup, please download and run [Diagnostic Tooling](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.Diagnostic.Tooling.exe), it will shows your device ID.
validations:
required: false
@@ -74,12 +74,12 @@ body:
- User Interface
- Snap Hutao Cloud
- Snap Hutao Account
- Checkin
- Checkin
- Wiki
- Announcement
- Other
validations:
required: true
required: true
- type: textarea
id: what-happened
@@ -107,4 +107,3 @@ body:
options:
- label: I believe the description above is detail enough to allow developers to reproduce the issue
required: true

View File

@@ -13,4 +13,8 @@ updates:
groups:
packages:
patterns:
- "*"
- "*"
- package-ecosystem: "github-actions"
directory: "/.github/workflows" # GitHub Workflows
schedule:
interval: "weekly"

View File

@@ -13,16 +13,28 @@ on:
- '**.md'
- 'LICENSE'
- '**.yml'
pull_request:
branches:
- develop
paths-ignore:
- '.gitattributes'
- '.github/**'
- '.gitignore'
- '.gitmodules'
- '**.md'
- 'LICENSE'
- '**.yml'
- '**.resx'
jobs:
build:
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4.0.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0
@@ -34,20 +46,21 @@ jobs:
VERSION_API_TOKEN: ${{ secrets.VERSION_API_TOKEN }}
- name: Sign Msix
if: success() && github.event_name != 'pull_request'
shell: pwsh
run: |
[System.Convert]::FromBase64String("${{ secrets.CERTIFICATE }}") | Set-Content -AsByteStream temp.pfx
signtool.exe sign /debug /v /a /fd SHA256 /f temp.pfx /p ${{ secrets.PW }} ${{ github.workspace }}\src\output\Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix
- name: Upload signed msix
if: success()
uses: actions/upload-artifact@v3
if: success() && github.event_name != 'pull_request'
uses: actions/upload-artifact@v4
with:
name: Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}
path: ${{ github.workspace }}/src/output/Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix
- name: Add summary
if: success()
if: success() && github.event_name != 'pull_request'
shell: pwsh
run: |
$summary = "

View File

@@ -74,3 +74,4 @@ Refresh:
script:
- apt-get install -y curl
- curl -X PATCH "$PURGE_URL"
- curl -X POST -o /dev/null "$UPLOAD_OSS_URL"

View File

@@ -1,5 +1,5 @@
#tool "nuget:?package=nuget.commandline&version=6.5.0"
#addin nuget:?package=Cake.Http&version=3.0.2
#tool "nuget:?package=nuget.commandline&version=6.9.1"
#addin nuget:?package=Cake.Http&version=4.0.0
var target = Argument("target", "Build");
var configuration = Argument("configuration", "Release");
@@ -33,18 +33,28 @@ if (GitHubActions.IsRunningOnGitHubActions)
repoDir = GitHubActions.Environment.Workflow.Workspace.FullPath;
outputPath = System.IO.Path.Combine(repoDir, "src", "output");
var versionAuth = HasEnvironmentVariable("VERSION_API_TOKEN") ? EnvironmentVariable("VERSION_API_TOKEN") : throw new Exception("Cannot find VERSION_API_TOKEN");
version = HttpGet(
"https://internal.snapgenshin.cn/BuildIntergration/RequestNewVersion",
new HttpSettings
{
Headers = new Dictionary<string, string>
{
if (GitHubActions.Environment.PullRequest.IsPullRequest)
{
version = System.DateTime.Now.ToString("yyyy.M.d.0");
Information("Is Pull Request. Skip version.");
}
else
{
var versionAuth = HasEnvironmentVariable("VERSION_API_TOKEN") ? EnvironmentVariable("VERSION_API_TOKEN") : throw new Exception("Cannot find VERSION_API_TOKEN");
version = HttpGet(
"https://internal.snapgenshin.cn/BuildIntergration/RequestNewVersion",
new HttpSettings
{
Headers = new Dictionary<string, string>
{
{ "Authorization", versionAuth }
}
}
);
Information($"Version: {version}");
}
}
);
Information($"Version: {version}");
}
GitHubActions.Commands.SetOutputParameter("version", version);
}

View File

@@ -12,9 +12,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.0" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -23,10 +23,12 @@ internal static class ControlAnimationConstants
/// <summary>
/// 图像淡入
/// </summary>
public static readonly TimeSpan ImageFadeIn = TimeSpan.FromSeconds(0.3);
public static readonly TimeSpan ImageScaleFadeIn = TimeSpan.FromSeconds(0.3);
/// <summary>
/// 图像淡出
/// </summary>
public static readonly TimeSpan ImageFadeOut = TimeSpan.FromSeconds(0.2);
public static readonly TimeSpan ImageScaleFadeOut = TimeSpan.FromSeconds(0.2);
public static readonly TimeSpan ImageOpacityFadeInOut = TimeSpan.FromSeconds(1);
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Behavior;
[DependencyProperty("Period", typeof(TimeSpan))]
[DependencyProperty("Command", typeof(ICommand))]
[DependencyProperty("CommandParameter", typeof(object))]
internal sealed partial class PeriodicInvokeCommandOrOnActualThemeChangedBehavior : BehaviorBase<FrameworkElement>, IDisposable
{
private TaskCompletionSource acutalThemeChangedTaskCompletionSource = new();
private CancellationTokenSource periodicTimerCancellationTokenSource = new();
public void Dispose()
{
periodicTimerCancellationTokenSource.Dispose();
}
protected override bool Initialize()
{
AssociatedObject.ActualThemeChanged += OnActualThemeChanged;
return true;
}
protected override void OnAssociatedObjectLoaded()
{
RunCoreAsync().SafeForget();
}
protected override bool Uninitialize()
{
AssociatedObject.ActualThemeChanged -= OnActualThemeChanged;
return true;
}
private void OnActualThemeChanged(FrameworkElement sender, object args)
{
acutalThemeChangedTaskCompletionSource.TrySetResult();
periodicTimerCancellationTokenSource.Cancel();
}
private void TryExecuteCommand()
{
if (AssociatedObject is null)
{
return;
}
if (Command is not null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}
}
private async ValueTask RunCoreAsync()
{
using (PeriodicTimer timer = new(Period))
{
do
{
if (!IsAttached)
{
break;
}
ITaskContext taskContext = Ioc.Default.GetRequiredService<ITaskContext>();
await taskContext.SwitchToMainThreadAsync();
TryExecuteCommand();
await taskContext.SwitchToBackgroundAsync();
try
{
Task nextTickTask = timer.WaitForNextTickAsync(periodicTimerCancellationTokenSource.Token).AsTask();
await Task.WhenAny(nextTickTask, acutalThemeChangedTaskCompletionSource.Task).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
acutalThemeChangedTaskCompletionSource = new();
periodicTimerCancellationTokenSource = new();
}
while (true);
}
}
}

View File

@@ -0,0 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Brush;
internal sealed class ColorSegmentCollection : List<IColorSegment>
{
}

View File

@@ -9,5 +9,5 @@ internal interface IColorSegment
{
Color Color { get; }
double Value { get; }
double Value { get; set; }
}

View File

@@ -9,36 +9,48 @@ using System.Runtime.InteropServices;
namespace Snap.Hutao.Control.Brush;
[DependencyProperty("Source", typeof(List<IColorSegment>), default!, nameof(OnSourceChanged))]
[DependencyProperty("Source", typeof(ColorSegmentCollection), default!, nameof(OnSourceChanged))]
internal sealed partial class SegmentedBar : ContentControl
{
private readonly LinearGradientBrush brush = new() { StartPoint = new(0, 0), EndPoint = new(1, 0), };
public SegmentedBar()
{
HorizontalContentAlignment = HorizontalAlignment.Stretch;
VerticalContentAlignment = VerticalAlignment.Stretch;
Content = new Rectangle()
{
Fill = brush,
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
};
}
private static void OnSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
SegmentedBar segmentedBar = (SegmentedBar)obj;
UpdateLinearGradientBrush((SegmentedBar)obj);
}
private static void UpdateLinearGradientBrush(SegmentedBar segmentedBar)
{
GradientStopCollection collection = segmentedBar.brush.GradientStops;
collection.Clear();
if (args.NewValue as List<IColorSegment> is [_, ..] list)
ColorSegmentCollection segmentCollection = segmentedBar.Source;
double total = segmentCollection.Sum(seg => seg.Value);
if (total is 0D)
{
double total = list.Sum(seg => seg.Value);
double offset = 0;
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(list))
{
collection.Add(new() { Color = segment.Color, Offset = offset, });
offset += segment.Value / total;
collection.Add(new() { Color = segment.Color, Offset = offset, });
}
return;
}
double offset = 0;
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(segmentCollection))
{
collection.Add(new() { Color = segment.Color, Offset = offset, });
offset += segment.Value / total;
collection.Add(new() { Color = segment.Color, Offset = offset, });
}
}
}

View File

@@ -4,7 +4,6 @@
using CommunityToolkit.WinUI.Collections;
using CommunityToolkit.WinUI.Helpers;
using Microsoft.UI.Xaml.Data;
using Snap.Hutao.Core.ExceptionService;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -208,13 +207,11 @@ internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, IN
public void Add(T item)
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Add(item);
}
public void Clear()
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Clear();
}
@@ -230,7 +227,6 @@ internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, IN
public bool Remove(T item)
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Remove(item);
return true;
}
@@ -243,7 +239,6 @@ internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, IN
public void Insert(int index, T item)
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Insert(index, item);
}

View File

@@ -0,0 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control;
internal interface IXamlElementAccessor;

View File

@@ -192,7 +192,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
await AnimationBuilder
.Create()
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageFadeIn)
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageScaleFadeIn)
.StartAsync(this, token)
.ConfigureAwait(true);
}
@@ -213,7 +213,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
await AnimationBuilder
.Create()
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageFadeOut)
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageScaleFadeOut)
.StartAsync(this, token)
.ConfigureAwait(true);
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32;
using Windows.UI;
namespace Snap.Hutao.Control.Theme;
internal static class KnownColors
{
public static readonly Color Orange = StructMarshal.Color(0xFFBC6932);
public static readonly Color Purple = StructMarshal.Color(0xFFA156E0);
public static readonly Color Blue = StructMarshal.Color(0xFF5180CB);
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Core.IO;
namespace Snap.Hutao.Core.Caching;
@@ -10,7 +9,7 @@ namespace Snap.Hutao.Core.Caching;
/// 为图像缓存提供抽象
/// </summary>
[HighQuality]
internal interface IImageCache : ICastService
internal interface IImageCache
{
/// <summary>
/// Gets the file path containing cached item for given Uri

View File

@@ -42,10 +42,20 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
private string? baseFolder;
private string? cacheFolder;
private string CacheFolder
{
get => LazyInitializer.EnsureInitialized(ref cacheFolder, () =>
{
baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
return info.FullName;
});
}
/// <inheritdoc/>
public void RemoveInvalid()
{
RemoveInternal(Directory.GetFiles(GetCacheFolder()).Where(file => IsFileInvalid(file, false)));
RemoveCore(Directory.GetFiles(CacheFolder).Where(file => IsFileInvalid(file, false)));
}
/// <inheritdoc/>
@@ -62,7 +72,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
return;
}
string folder = GetCacheFolder();
string folder = CacheFolder;
string[] files = Directory.GetFiles(folder);
List<string> filesToDelete = [];
@@ -75,16 +85,16 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
}
}
RemoveInternal(filesToDelete);
RemoveCore(filesToDelete);
}
/// <inheritdoc/>
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
{
string fileName = GetCacheFileName(uri);
string filePath = Path.Combine(GetCacheFolder(), fileName);
string filePath = Path.Combine(CacheFolder, fileName);
if (File.Exists(filePath) && new FileInfo(filePath).Length != 0)
if (!IsFileInvalid(filePath))
{
return filePath;
}
@@ -94,10 +104,12 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
{
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
{
logger.LogDebug("Begin downloading image file from '{Uri}' to '{File}'", uri, filePath);
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
}
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
{
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", uri);
await task.ConfigureAwait(false);
}
@@ -115,7 +127,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
public ValueFile GetFileFromCategoryAndName(string category, string fileName)
{
Uri dummyUri = Web.HutaoEndpoints.StaticRaw(category, fileName).ToUri();
return Path.Combine(GetCacheFolder(), GetCacheFileName(dummyUri));
return Path.Combine(CacheFolder, GetCacheFileName(dummyUri));
}
private static string GetCacheFileName(Uri uri)
@@ -137,17 +149,18 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
return fileInfo.Length == 0;
}
private void RemoveInternal(IEnumerable<string> filePaths)
private void RemoveCore(IEnumerable<string> filePaths)
{
foreach (string filePath in filePaths)
{
try
{
File.Delete(filePath);
logger.LogInformation("Remove cached image succeed:{File}", filePath);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Remove Cache Image Failed:{File}", filePath);
logger.LogWarning(ex, "Remove cached image failed:{File}", filePath);
}
}
}
@@ -155,14 +168,17 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
[SuppressMessage("", "SH003")]
private async Task DownloadFileAsync(Uri uri, string baseFile)
{
logger.LogInformation("Begin downloading for {Uri}", uri);
int retryCount = 0;
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
while (retryCount < 3)
{
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
{
if (message.RequestMessage is { RequestUri: { } target } && target != uri)
{
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
}
if (message.IsSuccessStatusCode)
{
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
@@ -181,7 +197,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
{
retryCount++;
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
logger.LogInformation("Retry {Uri} after {Delay}.", uri, delay);
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
await Task.Delay(delay).ConfigureAwait(false);
break;
}
@@ -192,18 +208,4 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
}
}
}
private string GetCacheFolder()
{
if (cacheFolder is not null)
{
return cacheFolder;
}
baseFolder ??= serviceProvider.GetRequiredService<RuntimeOptions>().LocalCache;
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
cacheFolder = info.FullName;
return cacheFolder;
}
}

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Core.Database;
internal static class QueryableExtension
{
/// <summary>
/// source.Where(predicate).ExecuteDelete()
/// <code>source.Where(predicate).ExecuteDelete()</code>
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <param name="source">源</param>
@@ -27,7 +27,7 @@ internal static class QueryableExtension
}
/// <summary>
/// source.Where(predicate).ExecuteDeleteAsync(token)
/// <code>source.Where(predicate).ExecuteDeleteAsync(token)</code>
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <param name="source">源</param>

View File

@@ -6,6 +6,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
/// <summary>
/// 可转换类型服务
/// </summary>
[Obsolete("Not useful anymore")]
internal interface ICastService
{
}

View File

@@ -1,18 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
/// <summary>
/// 有名称的对象
/// 指示该对象可通过名称区分
/// </summary>
[Obsolete("无意义的接口")]
[HighQuality]
internal interface INamedService
{
/// <summary>
/// 名称
/// </summary>
string Name { get; }
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
/// <summary>
/// 海外服/HoYoLAB 可区分
/// </summary>
[Obsolete("Use IOverseaSupportFactory instead")]
internal interface IOverseaSupport
{
/// <summary>
/// 是否为 海外服/HoYoLAB
/// </summary>
public bool IsOversea { get; }
}

View File

@@ -17,6 +17,7 @@ internal static class CastServiceExtension
/// <typeparam name="T">目标转换类型</typeparam>
/// <param name="service">对象</param>
/// <returns>转换类型后的对象</returns>
[Obsolete("Not useful anymore")]
public static T? As<T>(this ICastService service)
where T : class
{

View File

@@ -51,8 +51,13 @@ internal static class DependencyInjection
CultureOptions cultureOptions = serviceProvider.GetRequiredService<CultureOptions>();
cultureOptions.SystemCulture = CultureInfo.CurrentCulture;
ILogger<CultureOptions> logger = serviceProvider.GetRequiredService<ILogger<CultureOptions>>();
logger.LogDebug("System Culture: {System}", cultureOptions.SystemCulture);
CultureInfo cultureInfo = cultureOptions.CurrentCulture;
logger.LogDebug("Current Culture: {Current}", cultureInfo);
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
CultureInfo.CurrentCulture = cultureInfo;
@@ -63,6 +68,7 @@ internal static class DependencyInjection
SH.Culture = cultureInfo;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InitializeConsoleWindow(this IServiceProvider serviceProvider)
{
_ = serviceProvider.GetRequiredService<ConsoleWindowLifeTime>();

View File

@@ -47,17 +47,13 @@ internal static class IocConfiguration
{
if (context.Database.GetPendingMigrations().Any())
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("[Database] Performing AppDbContext Migrations");
#endif
context.Database.Migrate();
}
}
builder
#if DEBUG
.EnableSensitiveDataLogging()
#endif
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
.UseSqlite(sqlConnectionString);
}

View File

@@ -17,6 +17,6 @@ internal readonly struct MeasureExecutionToken : IDisposable
public void Dispose()
{
logger.LogInformation("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
logger.LogDebug("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
}
}

View File

@@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.ExceptionService;
/// 数据库损坏异常
/// </summary>
[HighQuality]
[Obsolete("Use HutaoException instead")]
internal sealed class DatabaseCorruptedException : Exception
{
/// <summary>

View File

@@ -18,6 +18,12 @@ internal sealed class HutaoException : Exception
public HutaoExceptionKind Kind { get; private set; }
[DoesNotReturn]
public static HutaoException Throw(HutaoExceptionKind kind, string message, Exception? innerException = default)
{
throw new HutaoException(kind, message, innerException);
}
public static void ThrowIf(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default)
{
if (condition)
@@ -25,4 +31,10 @@ internal sealed class HutaoException : Exception
throw new HutaoException(kind, message, innerException);
}
}
public static HutaoException ServiceTypeCastFailed<TFrom, TTo>(string name, Exception? innerException = default)
{
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
throw new HutaoException(HutaoExceptionKind.ServiceTypeCastFailed, message, innerException);
}
}

View File

@@ -6,4 +6,7 @@ namespace Snap.Hutao.Core.ExceptionService;
internal enum HutaoExceptionKind
{
None,
ServiceTypeCastFailed,
FileSystemCreateFileInsufficientPermissions,
PrivateNamedPipeContentHashIncorrect,
}

View File

@@ -8,6 +8,7 @@ namespace Snap.Hutao.Core.ExceptionService;
/// 用户的计算机中的某些设置不符合要求
/// </summary>
[HighQuality]
[Obsolete("Use HutaoException instead")]
internal sealed class RuntimeEnvironmentException : Exception
{
/// <summary>

View File

@@ -13,6 +13,7 @@ namespace Snap.Hutao.Core.ExceptionService;
/// </summary>
[HighQuality]
[System.Diagnostics.StackTraceHidden]
[Obsolete("Use HutaoException instead")]
internal static class ThrowHelper
{
[DoesNotReturn]

View File

@@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.ExceptionService;
/// 用户数据损坏异常
/// </summary>
[HighQuality]
[Obsolete("Use HutaoException instead")]
internal sealed class UserdataCorruptedException : Exception
{
/// <summary>

View File

@@ -29,17 +29,19 @@ internal sealed unsafe class LoopbackManager : ObservableObject
INET_FIREWALL_APP_CONTAINER* pContainers = default;
try
{
WIN32_ERROR error = NetworkIsolationEnumAppContainers(NETISO_FLAG.NETISO_FLAG_MAX, out uint acCount, out pContainers);
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
for (uint i = 0; i < acCount; i++)
{
INET_FIREWALL_APP_CONTAINER* pContainer = pContainers + i;
ReadOnlySpan<char> appContainerName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pContainer->appContainerName);
if (appContainerName.Equals(runtimeOptions.FamilyName, StringComparison.Ordinal))
WIN32_ERROR error = NetworkIsolationEnumAppContainers(NETISO_FLAG.NETISO_FLAG_MAX, out uint acCount, out pContainers);
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
for (uint i = 0; i < acCount; i++)
{
ConvertSidToStringSidW(pContainer->appContainerSid, out PWSTR stringSid);
hutaoContainerStringSID = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid).ToString();
break;
INET_FIREWALL_APP_CONTAINER* pContainer = pContainers + i;
ReadOnlySpan<char> appContainerName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pContainer->appContainerName);
if (appContainerName.Equals(runtimeOptions.FamilyName, StringComparison.Ordinal))
{
ConvertSidToStringSidW(pContainer->appContainerSid, out PWSTR stringSid);
hutaoContainerStringSID = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid).ToString();
break;
}
}
}
}
@@ -49,16 +51,18 @@ internal sealed unsafe class LoopbackManager : ObservableObject
_ = NetworkIsolationFreeAppContainers(pContainers);
}
WIN32_ERROR error2 = NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error2));
for (uint i = 0; i < accCount; i++)
{
ConvertSidToStringSidW((pSids + i)->Sid, out PWSTR stringSid);
ReadOnlySpan<char> stringSidSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid);
if (stringSidSpan.Equals(hutaoContainerStringSID, StringComparison.Ordinal))
WIN32_ERROR error = NetworkIsolationGetAppContainerConfig(out uint accCount, out SID_AND_ATTRIBUTES* pSids);
Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(error));
for (uint i = 0; i < accCount; i++)
{
IsLoopbackEnabled = true;
break;
ConvertSidToStringSidW((pSids + i)->Sid, out PWSTR stringSid);
ReadOnlySpan<char> stringSidSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(stringSid);
if (stringSidSpan.Equals(hutaoContainerStringSID, StringComparison.Ordinal))
{
IsLoopbackEnabled = true;
break;
}
}
}
}

View File

@@ -29,7 +29,7 @@ internal readonly struct TempFile : IDisposable
}
catch (UnauthorizedAccessException ex)
{
ThrowHelper.RuntimeEnvironment(SH.CoreIOTempFileCreateFail, ex);
HutaoException.Throw(HutaoExceptionKind.FileSystemCreateFileInsufficientPermissions, SH.CoreIOTempFileCreateFail, ex);
}
if (delete)

View File

@@ -148,17 +148,15 @@ internal sealed partial class Activation : IActivation
await taskContext.SwitchToBackgroundAsync();
serviceProvider
.GetRequiredService<IMetadataService>()
.As<IMetadataServiceInitialization>()?
.InitializeInternalAsync()
.SafeForget();
if (serviceProvider.GetRequiredService<IMetadataService>() is IMetadataServiceInitialization metadataServiceInitialization)
{
metadataServiceInitialization.InitializeInternalAsync().SafeForget();
}
serviceProvider
.GetRequiredService<IHutaoUserService>()
.As<IHutaoUserServiceInitialization>()?
.InitializeInternalAsync()
.SafeForget();
if (serviceProvider.GetRequiredService<IHutaoUserService>() is IHutaoUserServiceInitialization hutaoUserServiceInitialization)
{
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
}
serviceProvider
.GetRequiredService<IDiscordService>()

View File

@@ -49,7 +49,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
{
byte[] content = new byte[header->ContentLength];
serverStream.ReadAtLeast(content, header->ContentLength, false);
ThrowHelper.InvalidDataIf(XxHash64.HashToUInt64(content) != header->Checksum, "PipePacket Content Hash incorrect");
HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, HutaoExceptionKind.PrivateNamedPipeContentHashIncorrect, "PipePacket Content Hash incorrect");
return content;
}

View File

@@ -18,20 +18,11 @@ internal static class DateTimeOffsetExtension
return defaultValue;
}
try
return value switch
{
return DateTimeOffset.FromUnixTimeSeconds(value);
}
catch (ArgumentOutOfRangeException)
{
try
{
return DateTimeOffset.FromUnixTimeMilliseconds(value);
}
catch (ArgumentOutOfRangeException)
{
return defaultValue;
}
}
>= -62135596800 and <= 253402300799 => DateTimeOffset.FromUnixTimeSeconds(value),
>= -62135596800000 and <= 253402300799999 => DateTimeOffset.FromUnixTimeMilliseconds(value),
_ => defaultValue,
};
}
}

View File

@@ -46,34 +46,6 @@ internal static partial class EnumerableExtension
return first;
}
public static string JoinToString<T>(this IEnumerable<T> source, char separator, Action<StringBuilder, T> selector)
{
StringBuilder resultBuilder = new();
IEnumerator<T> enumerator = source.GetEnumerator();
if (!enumerator.MoveNext())
{
return string.Empty;
}
T first = enumerator.Current;
selector(resultBuilder, first);
if (!enumerator.MoveNext())
{
return resultBuilder.ToString();
}
do
{
resultBuilder.Append(separator);
selector(resultBuilder, enumerator.Current);
}
while (enumerator.MoveNext());
return resultBuilder.ToString();
}
public static string JoinToString<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source, char separator, Action<StringBuilder, TKey, TValue> selector)
{
StringBuilder resultBuilder = new();

View File

@@ -27,7 +27,7 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMax
public MainWindow(IServiceProvider serviceProvider)
{
InitializeComponent();
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true, false);
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true);
this.InitializeController(serviceProvider);
}

View File

@@ -0,0 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Message;
internal sealed class BackgroundImageTypeChangedMessage;

View File

@@ -0,0 +1,627 @@
// <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("20240219020258_AddPerferredUidOnUser")]
partial class AddPerferredUidOnUser
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
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.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,29 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Snap.Hutao.Migrations
{
/// <inheritdoc />
public partial class AddPerferredUidOnUser : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PreferredUid",
table: "users",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PreferredUid",
table: "users");
}
}
}

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.1");
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
@@ -503,6 +503,9 @@ namespace Snap.Hutao.Migrations
b.Property<string>("Mid")
.HasColumnType("TEXT");
b.Property<string>("PreferredUid")
.HasColumnType("TEXT");
b.Property<string>("SToken")
.HasColumnType("TEXT")
.HasColumnName("Stoken");

View File

@@ -11,10 +11,16 @@ internal static class CollectionsNameValue
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(x.ToString(), x))];
}
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> codiction)
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> condition)
where TEnum : struct, Enum
{
return [.. Enum.GetValues<TEnum>().Where(codiction).Select(x => new NameValue<TEnum>(x.ToString(), x))];
return [.. Enum.GetValues<TEnum>().Where(condition).Select(x => new NameValue<TEnum>(x.ToString(), x))];
}
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, string> nameSelector)
where TEnum : struct, Enum
{
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(nameSelector(x), x))];
}
public static List<NameValue<TSource>> From<TSource>(IEnumerable<TSource> sources, Func<TSource, string> nameSelector)

View File

@@ -13,6 +13,7 @@ internal sealed partial class SettingEntry
public const string Culture = "Culture";
public const string SystemBackdropType = "SystemBackdropType";
public const string BackgroundImageType = "BackgroundImageType";
public const string AnnouncementRegion = "AnnouncementRegion";
@@ -42,6 +43,7 @@ internal sealed partial class SettingEntry
public const string LaunchIsUseCloudThirdPartyMobile = "Launch.IsUseCloudThirdPartyMobile";
public const string LaunchIsWindowsHDREnabled = "Launch.IsWindowsHDREnabled";
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
public const string LaunchUseBetterGenshinImpactAutomation = "Launch.UseBetterGenshinImpactAutomation";
public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying";
[Obsolete("不再支持多开")]
@@ -49,4 +51,4 @@ internal sealed partial class SettingEntry
[Obsolete("不再使用 PowerShell")]
public const string PowerShellPath = "PowerShellPath";
}
}

View File

@@ -71,6 +71,8 @@ internal sealed class User : ISelectable, IReorderable, IMappingFrom<User, Cooki
public int Index { get; set; }
public string? PreferredUid { get; set; }
/// <summary>
/// 创建一个新的用户
/// </summary>

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutao"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.6.0" />
Version="1.9.7.0" />
<Properties>
<DisplayName>Snap Hutao</DisplayName>

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutaoDev"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.6.0" />
Version="1.9.7.0" />
<Properties>
<DisplayName>Snap Hutao Dev</DisplayName>

View File

@@ -145,7 +145,7 @@
<value>Save</value>
</data>
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
<value>Invalid URL</value>
<value>Invalid Uri</value>
</data>
<data name="ControlImageCompositionImageHttpRequest" xml:space="preserve">
<value>HTTP GET {0}</value>
@@ -285,7 +285,7 @@
<comment>Need EXACT same string in game</comment>
</data>
<data name="ModelIntrinsicAssociationTypeInazuma" xml:space="preserve">
<value>Inadzuma</value>
<value>Inazuma</value>
<comment>Need EXACT same string in game</comment>
</data>
<data name="ModelIntrinsicAssociationTypeLiyue" xml:space="preserve">
@@ -546,7 +546,7 @@
<value>Uploaded {1} wish records of UID: {0}, stored {2}</value>
</data>
<data name="ServerPassportLoginRequired" xml:space="preserve">
<value>Please login or register Hutao account first</value>
<value>Please login or register Snap Hutao account first</value>
</data>
<data name="ServerPassportLoginSucceed" xml:space="preserve">
<value>Login successfully</value>
@@ -558,7 +558,7 @@
<value>Password has been set successfully</value>
</data>
<data name="ServerPassportServiceEmailHasNotRegistered" xml:space="preserve">
<value>Current email adress is not registered</value>
<value>Current email address is not registered</value>
</data>
<data name="ServerPassportServiceEmailHasRegistered" xml:space="preserve">
<value>Current emaill address is registered</value>
@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Character Showcase: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>Bing Daily Wallpaper</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>Hutao Daily Wallpaper</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>Genshin Official Launcher Wallpaper</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>Local Random Image</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>No Wallpaper</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Failed to save development plan status</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>Parametric Transformer is ready</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>Missing permission, unable to set your Discord Activity.</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Exploring in Teyvat</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>Cannot reorder in Admin Mode</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Archive [{0}] added successfully</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Completed</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>Response stream does not contain valid content type</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Queued</value>
</data>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Launch Parameters</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>Auto start Better GUI for automation tasks after game launched</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>General</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>Backdrop Material</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>Image Copyright Information</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>Change the source of wallpaper, restart Snap Hutao to apply the change</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>Wallpaper Image</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>Images cache are saved here</value>
</data>
@@ -2544,10 +2583,10 @@
<value>Reset Image Resource</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
<value>Add Unlock Frame Rate Limit Option in Game Launcher Process Section</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
<value>Game Launcher - Unlock Frame Rate Limit</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>You need to move data in the directory manually, otherwise new user data will be created.</value>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Pameran Karakter: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Gagal menyimpan status rencana pengembangan</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>Parametric Transformer telah siap</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Menjelajahi di Teyvat</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Arsip [{0}] berhasil ditambahkan</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Selesai</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Mengantre</value>
</data>
@@ -2010,7 +2034,7 @@
<value>Pratinjau</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>Statistik</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>Senjata</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Argumen Awalan</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>Umum</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>Backdrop Material</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>Cache gambar disimpan di sini</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>キャラクターラインナップ:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>育成計画のステータスを保存できません</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>参量物質変化器は使用可能</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>テイワット大陸を探索中</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>ゲームランチャー</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>アーカイブ [{0}] を作成しました</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完了</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>待機中</value>
</data>
@@ -2010,7 +2034,7 @@
<value>一覧</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>統計</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>武器</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>コマンドラインパラメーター</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>一般</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>テーマ</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>イメージキャッシュはここに格納されます</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色橱窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>육성 계획 상태를 저장하지 못했습니다</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>매개 변수 변환기가 준비되었습니다</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陆中探索</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>게임 시작</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>아카이브 [{0}]가 추가되었습니다</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>완료</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>대기 중</value>
</data>
@@ -2010,7 +2034,7 @@
<value>개요</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>统计</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>무기</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>启动参数</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>보통</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>배경 테마</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>여기에 저장된 이미지 캐시</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Exibição de personagens: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Falha ao salvar o status do planejamento</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>O Transformador Paramétrico está pronto</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Explorando em Teyvat</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Inicializador</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Arquivo [{0}] adicionado com sucesso</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Concluído</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Em fila</value>
</data>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>Argumentos de inicialização</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>Geral</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>Material de pano de fundo</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>O cache de imagens é salvo aqui</value>
</data>
@@ -2532,7 +2571,7 @@
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打开背景图片文件夹</value>
<value>Abrir pasta do fundo</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>Resetar</value>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色橱窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>保存养成计划状态失败</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>参量质变仪已准备完成</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陆中探索</value>
</data>
@@ -1673,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完成</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>等待中</value>
</data>
@@ -2189,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>启动参数</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>常规</value>
</data>
@@ -2330,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材质</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>图片缓存 在此处存放</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>Демонстрация персонажей: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Не удалось сохранить статус плана разработки.</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>Преобразователь готов</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Исследование Тейвата</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>存档 [{0}] 添加成功</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>Завершено</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>В процессе</value>
</data>
@@ -2010,7 +2034,7 @@
<value>总览</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>统计</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>Оружие</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>启动参数</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>常规</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材质</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>图片缓存 在此处存放</value>
</data>

View File

@@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色櫥窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>保存養成計劃狀態失敗</value>
</data>
@@ -824,6 +839,9 @@
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>參量質變儀已準備完成</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陸中探索</value>
</data>
@@ -1391,6 +1409,9 @@
<data name="ViewLaunchGameHeader" xml:space="preserve">
<value>啟動遊戲</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>存檔 [{0}] 添加成功</value>
</data>
@@ -1670,6 +1691,9 @@
<data name="ViewModelWelcomeDownloadSummaryComplete" xml:space="preserve">
<value>完成</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>待處理</value>
</data>
@@ -2010,7 +2034,7 @@
<value>總覽</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>統計</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>武器</value>
@@ -2186,6 +2210,12 @@
<data name="ViewPageLaunchGameArgumentsHeader" xml:space="preserve">
<value>啟動參數</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>一般</value>
</data>
@@ -2327,6 +2357,15 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材質</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>圖片暫存存放在此</value>
</data>

View File

@@ -5,6 +5,7 @@ using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.BackgroundImage;
using Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Service;
@@ -15,6 +16,7 @@ internal sealed partial class AppOptions : DbStoreOptions
{
private bool? isEmptyHistoryWishVisible;
private BackdropType? backdropType;
private BackgroundImageType? backgroundImageType;
private Region? region;
private string? geetestCustomCompositeUrl;
@@ -28,8 +30,16 @@ internal sealed partial class AppOptions : DbStoreOptions
public BackdropType BackdropType
{
get => GetOption(ref backdropType, SettingEntry.SystemBackdropType, v => Enum.Parse<BackdropType>(v), BackdropType.Mica).Value;
set => SetOption(ref backdropType, SettingEntry.SystemBackdropType, value, value => value.ToStringOrEmpty());
get => GetOption(ref backdropType, SettingEntry.SystemBackdropType, EnumParse<BackdropType>, BackdropType.Mica).Value;
set => SetOption(ref backdropType, SettingEntry.SystemBackdropType, value, EnumToStringOrEmpty);
}
public List<NameValue<BackgroundImageType>> BackgroundImageTypes { get; } = CollectionsNameValue.FromEnum<BackgroundImageType>(type => type.GetLocalizedDescription());
public BackgroundImageType BackgroundImageType
{
get => GetOption(ref backgroundImageType, SettingEntry.BackgroundImageType, EnumParse<BackgroundImageType>, BackgroundImageType.None).Value;
set => SetOption(ref backgroundImageType, SettingEntry.BackgroundImageType, value, EnumToStringOrEmpty);
}
public Lazy<List<NameValue<Region>>> LazyRegions { get; } = new(KnownRegions.Get);
@@ -45,4 +55,16 @@ internal sealed partial class AppOptions : DbStoreOptions
get => GetOption(ref geetestCustomCompositeUrl, SettingEntry.GeetestCustomCompositeUrl);
set => SetOption(ref geetestCustomCompositeUrl, SettingEntry.GeetestCustomCompositeUrl, value);
}
private static T? EnumParse<T>(string input)
where T : struct, Enum
{
return Enum.Parse<T>(input);
}
private static string EnumToStringOrEmpty<T>(T? input)
where T : struct, Enum
{
return input.ToStringOrEmpty();
}
}

View File

@@ -2,17 +2,14 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Core;
using System.IO;
using System.Runtime.InteropServices;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace Snap.Hutao.Service.BackgroundImage;
internal sealed class BackgroundImage
{
public string Path { get; set; } = default!;
public BitmapImage ImageSource { get; set; } = default!;
public Color AccentColor { get; set; }

View File

@@ -0,0 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Web.Hutao.Wallpaper;
namespace Snap.Hutao.Service.BackgroundImage;
[Injection(InjectAs.Singleton)]
internal sealed partial class BackgroundImageOptions : ObservableObject
{
private Wallpaper? wallpaper;
public Wallpaper? Wallpaper { get => wallpaper; set => SetProperty(ref wallpaper, value); }
}

View File

@@ -5,11 +5,11 @@ using Snap.Hutao.Control.Media;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Content;
using Snap.Hutao.Web.Hutao.Wallpaper;
using Snap.Hutao.Web.Response;
using Snap.Hutao.Win32.Foundation;
using System.IO;
using System.Runtime.InteropServices;
using Windows.Graphics.Imaging;
namespace Snap.Hutao.Service.BackgroundImage;
@@ -20,11 +20,13 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
{
private static readonly HashSet<string> AllowedFormats = [".bmp", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".tiff", ".webp"];
private readonly BackgroundImageOptions backgroundImageOptions;
private readonly IServiceProvider serviceProvider;
private readonly RuntimeOptions runtimeOptions;
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
private HashSet<string> backgroundPathSet;
private HashSet<string> currentBackgroundPathSet;
public async ValueTask<ValueResult<bool, BackgroundImage>> GetNextBackgroundImageAsync(BackgroundImage? previous)
{
@@ -35,18 +37,31 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
return new(false, default!);
}
string path = System.Random.Shared.GetItems(backgroundSet.ToArray(), 1)[0];
string path = System.Random.Shared.GetItems([..backgroundSet], 1)[0];
backgroundSet.Remove(path);
await taskContext.SwitchToMainThreadAsync();
if (string.Equals(path, previous?.ImageSource.UriSource.ToString(), StringComparison.OrdinalIgnoreCase))
if (string.Equals(path, previous?.Path, StringComparison.OrdinalIgnoreCase))
{
return new(false, default!);
}
using (FileStream fileStream = File.OpenRead(path))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream.AsRandomAccessStream());
BitmapDecoder decoder;
try
{
decoder = await BitmapDecoder.CreateAsync(fileStream.AsRandomAccessStream());
}
catch (COMException comException)
{
if (comException.HResult != HRESULT.E_FAIL)
{
throw;
}
return new(false, default!);
}
SoftwareBitmap softwareBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight);
Bgra32 accentColor = softwareBitmap.GetAccentColor();
@@ -54,6 +69,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
BackgroundImage background = new()
{
Path = path,
ImageSource = new(path.ToUri()),
AccentColor = accentColor,
Luminance = accentColor.Luminance,
@@ -65,32 +81,55 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
private async ValueTask<HashSet<string>> SkipOrInitBackgroundAsync()
{
if (backgroundPathSet is null || backgroundPathSet.Count <= 0)
switch (appOptions.BackgroundImageType)
{
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
Directory.CreateDirectory(backgroundFolder);
backgroundPathSet = Directory
.GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories)
.Where(path => AllowedFormats.Contains(Path.GetExtension(path)))
.ToHashSet();
// No image found
if (backgroundPathSet.Count <= 0)
{
ResourceClient resourceClient = serviceProvider.GetRequiredService<ResourceClient>();
string launguageCode = serviceProvider.GetRequiredService<CultureOptions>().LanguageCode;
LaunchScheme scheme = launguageCode is "zh-cn"
? KnownLaunchSchemes.Get().First(scheme => !scheme.IsOversea && scheme.IsNotCompatOnly)
: KnownLaunchSchemes.Get().First(scheme => scheme.IsOversea && scheme.IsNotCompatOnly);
Response<GameContent> response = await resourceClient.GetContentAsync(scheme, launguageCode).ConfigureAwait(false);
if (response is { Data.Advertisement.Background: string url })
case BackgroundImageType.LocalFolder:
{
ValueFile file = await serviceProvider.GetRequiredService<IImageCache>().GetFileFromCacheAsync(url.ToUri()).ConfigureAwait(false);
backgroundPathSet = [file];
if (currentBackgroundPathSet is not { Count: > 0 })
{
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
Directory.CreateDirectory(backgroundFolder);
currentBackgroundPathSet = Directory
.GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories)
.Where(path => AllowedFormats.Contains(Path.GetExtension(path)))
.ToHashSet();
}
backgroundImageOptions.Wallpaper = default;
break;
}
case BackgroundImageType.HutaoBing:
await SetCurrentBackgroundPathSetAsync(client => client.GetBingWallpaperAsync()).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoDaily:
await SetCurrentBackgroundPathSetAsync(client => client.GetTodayWallpaperAsync()).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoOfficialLauncher:
await SetCurrentBackgroundPathSetAsync(client => client.GetLauncherWallpaperAsync()).ConfigureAwait(false);
break;
}
currentBackgroundPathSet ??= [];
return currentBackgroundPathSet;
async Task SetCurrentBackgroundPathSetAsync(Func<HutaoWallpaperClient, ValueTask<Response<Wallpaper>>> responseFactory)
{
HutaoWallpaperClient wallpaperClient = serviceProvider.GetRequiredService<HutaoWallpaperClient>();
Response<Wallpaper> response = await responseFactory(wallpaperClient).ConfigureAwait(false);
if (response is { Data: Wallpaper wallpaper })
{
await taskContext.SwitchToMainThreadAsync();
backgroundImageOptions.Wallpaper = wallpaper;
await taskContext.SwitchToBackgroundAsync();
if (wallpaper.Url is { } url)
{
ValueFile file = await serviceProvider.GetRequiredService<IImageCache>().GetFileFromCacheAsync(url).ConfigureAwait(false);
currentBackgroundPathSet = [file];
}
}
}
return backgroundPathSet;
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.BackgroundImage;
[Localization]
internal enum BackgroundImageType
{
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeNone))]
None,
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeLocalFolder))]
LocalFolder,
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeBing))]
HutaoBing,
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeDaily))]
HutaoDaily,
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeLauncher))]
HutaoOfficialLauncher,
}

View File

@@ -7,7 +7,7 @@ using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Service.Inventroy;
using Snap.Hutao.Service.Inventory;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.Cultivation;
using System.Collections.ObjectModel;

View File

@@ -132,13 +132,6 @@ internal static class DiscordController
return;
}
// Actually requires a discord client to be running on Windows platform.
// If not, the following creation code will throw.
if (System.Diagnostics.Process.GetProcessesByName("Discord").Length <= 0)
{
return;
}
lock (SyncRoot)
{
DiscordCreateParams @params = default;

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT license.
using Snap.Hutao.Core;
using Snap.Hutao.Service.Notification;
using System.Diagnostics;
namespace Snap.Hutao.Service.Discord;
@@ -9,22 +11,69 @@ namespace Snap.Hutao.Service.Discord;
[Injection(InjectAs.Singleton, typeof(IDiscordService))]
internal sealed partial class DiscordService : IDiscordService, IDisposable
{
private readonly IInfoBarService infoBarService;
private readonly RuntimeOptions runtimeOptions;
private bool isInitialized;
public async ValueTask SetPlayingActivityAsync(bool isOversea)
{
_ = isOversea
? await DiscordController.SetPlayingGenshinImpactAsync().ConfigureAwait(false)
: await DiscordController.SetPlayingYuanShenAsync().ConfigureAwait(false);
if (IsSupported())
{
_ = isOversea
? await DiscordController.SetPlayingGenshinImpactAsync().ConfigureAwait(false)
: await DiscordController.SetPlayingYuanShenAsync().ConfigureAwait(false);
}
}
public async ValueTask SetNormalActivityAsync()
{
_ = await DiscordController.SetDefaultActivityAsync(runtimeOptions.AppLaunchTime).ConfigureAwait(false);
if (IsSupported())
{
_ = await DiscordController.SetDefaultActivityAsync(runtimeOptions.AppLaunchTime).ConfigureAwait(false);
}
}
public void Dispose()
{
DiscordController.Stop();
}
private bool IsSupported()
{
try
{
// Actually requires a discord client to be running on Windows platform.
// If not, discord core creation code will throw.
Process[] discordProcesses = Process.GetProcessesByName("Discord");
if (discordProcesses.Length <= 0)
{
return false;
}
foreach (Process process in discordProcesses)
{
try
{
_ = process.Handle;
}
catch (Exception)
{
if (!isInitialized)
{
infoBarService.Warning(SH.ServiceDiscordActivityElevationRequiredHint);
}
return false;
}
}
return true;
}
finally
{
isInitialized = true;
}
}
}

View File

@@ -45,6 +45,7 @@ internal sealed class LaunchOptions : DbStoreOptions
private bool? isWindowsHDREnabled;
private AspectRatio? selectedAspectRatio;
private bool? useStarwardPlayTimeStatistics;
private bool? useBetterGenshinImpactAutomation;
private bool? setDiscordActivityWhenPlaying;
public LaunchOptions(IServiceProvider serviceProvider)
@@ -219,7 +220,7 @@ internal sealed class LaunchOptions : DbStoreOptions
get => selectedAspectRatio;
set
{
if (SetProperty(ref selectedAspectRatio, value) && value is AspectRatio aspectRatio)
if (SetProperty(ref selectedAspectRatio, value) && value is { } aspectRatio)
{
(ScreenWidth, ScreenHeight) = ((int)aspectRatio.Width, (int)aspectRatio.Height);
}
@@ -232,6 +233,12 @@ internal sealed class LaunchOptions : DbStoreOptions
set => SetOption(ref useStarwardPlayTimeStatistics, SettingEntry.LaunchUseStarwardPlayTimeStatistics, value);
}
public bool UseBetterGenshinImpactAutomation
{
get => GetOption(ref useBetterGenshinImpactAutomation, SettingEntry.LaunchUseBetterGenshinImpactAutomation, false);
set => SetOption(ref useBetterGenshinImpactAutomation, SettingEntry.LaunchUseBetterGenshinImpactAutomation, value);
}
public bool SetDiscordActivityWhenPlaying
{
get => GetOption(ref setDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, true);

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Windows.System;
namespace Snap.Hutao.Service.Game.Launching.Handler;
internal sealed class LaunchExecutionBetterGenshinImpactAutomationHandlder : ILaunchExecutionDelegateHandler
{
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
{
if (context.Options.UseBetterGenshinImpactAutomation)
{
context.Logger.LogInformation("Using BetterGI to automate gameplay");
await LaunchBetterGenshinImpactAsync(context).ConfigureAwait(false);
}
await next().ConfigureAwait(false);
}
private static async ValueTask LaunchBetterGenshinImpactAsync(LaunchExecutionContext context)
{
Uri betterGenshinImpactUri = "bettergi://start".ToUri();
if (await Launcher.QueryUriSupportAsync(betterGenshinImpactUri, LaunchQuerySupportType.Uri) is LaunchQuerySupportStatus.Available)
{
context.Logger.LogInformation("Waiting game window to be ready");
context.Process.WaitForInputIdle();
context.Logger.LogInformation("Launching BetterGI");
await Launcher.LaunchUriAsync(betterGenshinImpactUri);
}
}
}

View File

@@ -11,7 +11,7 @@ internal sealed class LaunchExecutionStarwardPlayTimeStatisticsHandler : ILaunch
{
if (context.Options.UseStarwardPlayTimeStatistics)
{
context.Logger.LogInformation("Using starward to count game time");
context.Logger.LogInformation("Using Starward to count game time");
await LaunchStarwardForPlayTimeStatisticsAsync(context).ConfigureAwait(false);
}
@@ -24,7 +24,7 @@ internal sealed class LaunchExecutionStarwardPlayTimeStatisticsHandler : ILaunch
Uri starwardPlayTimeUri = $"starward://playtime/{gameBiz}".ToUri();
if (await Launcher.QueryUriSupportAsync(starwardPlayTimeUri, LaunchQuerySupportType.Uri) is LaunchQuerySupportStatus.Available)
{
context.Logger.LogInformation("Launching starward");
context.Logger.LogInformation("Launching Starward");
await Launcher.LaunchUriAsync(starwardPlayTimeUri);
}
}

View File

@@ -34,6 +34,7 @@ internal sealed class LaunchExecutionUnlockFpsHandler : ILaunchExecutionDelegate
// The Unlocker can't unlock the process
context.Process.Kill();
return;
}
}

View File

@@ -25,6 +25,7 @@ internal sealed class LaunchExecutionInvoker
handlers.Enqueue(new LaunchExecutionSetDiscordActivityHandler());
handlers.Enqueue(new LaunchExecutionGameProcessStartHandler());
handlers.Enqueue(new LaunchExecutionStarwardPlayTimeStatisticsHandler());
handlers.Enqueue(new LaunchExecutionBetterGenshinImpactAutomationHandlder());
handlers.Enqueue(new LaunchExecutionUnlockFpsHandler());
handlers.Enqueue(new LaunchExecutionGameProcessExitHandler());
}
@@ -40,9 +41,9 @@ internal sealed class LaunchExecutionInvoker
if (handlers.TryDequeue(out ILaunchExecutionDelegateHandler? handler))
{
string typeName = TypeNameHelper.GetTypeDisplayName(handler, false);
context.Logger.LogInformation("Handler[{Handler}] begin execution", typeName);
context.Logger.LogInformation("Handler [{Handler}] begin execution", typeName);
await handler.OnExecutionAsync(context, () => InvokeHandlerAsync(context)).ConfigureAwait(false);
context.Logger.LogInformation("Handler[{Handler}] end execution", typeName);
context.Logger.LogInformation("Handler [{Handler}] end execution", typeName);
}
return context;

View File

@@ -1,14 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Abstraction;
namespace Snap.Hutao.Service.Hutao;
/// <summary>
/// 胡桃用户服务
/// </summary>
internal interface IHutaoUserService : ICastService
internal interface IHutaoUserService
{
/// <summary>
/// 异步初始化

View File

@@ -3,7 +3,7 @@
using Snap.Hutao.Model.Entity;
namespace Snap.Hutao.Service.Inventroy;
namespace Snap.Hutao.Service.Inventory;
internal interface IInventoryDbService
{

View File

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

View File

@@ -6,7 +6,7 @@ using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
namespace Snap.Hutao.Service.Inventroy;
namespace Snap.Hutao.Service.Inventory;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IInventoryDbService))]

View File

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

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Service.Metadata;
/// 元数据服务
/// </summary>
[HighQuality]
internal interface IMetadataService : ICastService
internal interface IMetadataService
{
IMemoryCache MemoryCache { get; }

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Navigation;
internal interface INavigationCurrent
{
Type? Current { get; }
}

View File

@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Service.Navigation;
/// <summary>
@@ -10,10 +8,5 @@ namespace Snap.Hutao.Service.Navigation;
/// </summary>
internal interface INavigationInitialization
{
/// <summary>
/// 使用指定的对象进行初始化
/// </summary>
/// <param name="navigationView">管理的 <see cref="NavigationView"/></param>
/// <param name="frame">管理的 <see cref="Frame"/></param>
void Initialize(NavigationView navigationView, Frame frame);
void Initialize(INavigationViewAccessor accessor);
}

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Service.Navigation;
/// 导航服务
/// </summary>
[HighQuality]
internal interface INavigationService : ICastService, INavigationCurrent
internal interface INavigationService : INavigationCurrent
{
/// <summary>
/// 导航到指定类型的页面
@@ -47,9 +47,4 @@ internal interface INavigationService : ICastService, INavigationCurrent
/// 尽可能尝试返回
/// </summary>
void GoBack();
}
internal interface INavigationCurrent
{
Type? Current { get; }
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control;
namespace Snap.Hutao.Service.Navigation;
internal interface INavigationViewAccessor : IXamlElementAccessor
{
NavigationView NavigationView { get; }
Frame Frame { get; }
}

View File

@@ -147,10 +147,10 @@ internal sealed class NavigationService : INavigationService, INavigationInitial
}
/// <inheritdoc/>
public void Initialize(NavigationView navigationView, Frame frame)
public void Initialize(INavigationViewAccessor accessor)
{
NavigationView = navigationView;
this.frame = frame;
NavigationView = accessor.NavigationView;
frame = accessor.Frame;
NavigationView.IsPaneOpen = LocalSetting.Get(SettingKeys.IsNavPaneOpen, true);
}

View File

@@ -12,12 +12,19 @@ internal static class SupportedCultures
[
ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")),
ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")),
/*ToNameValue(CultureInfo.GetCultureInfo("de")),*/
ToNameValue(CultureInfo.GetCultureInfo("en")),
/*ToNameValue(CultureInfo.GetCultureInfo("es")),*/
/*ToNameValue(CultureInfo.GetCultureInfo("fr")),*/
ToNameValue(CultureInfo.GetCultureInfo("id")),
/*ToNameValue(CultureInfo.GetCultureInfo("it")),*/
ToNameValue(CultureInfo.GetCultureInfo("ja")),
ToNameValue(CultureInfo.GetCultureInfo("pt")),
ToNameValue(CultureInfo.GetCultureInfo("ko")),
ToNameValue(CultureInfo.GetCultureInfo("pt")),
ToNameValue(CultureInfo.GetCultureInfo("ru")),
/*ToNameValue(CultureInfo.GetCultureInfo("th")),*/
/*ToNameValue(CultureInfo.GetCultureInfo("tr")),*/
/*ToNameValue(CultureInfo.GetCultureInfo("vi")),*/
];
public static List<NameValue<CultureInfo>> Get()

View File

@@ -90,8 +90,6 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
await userFingerprintService.TryInitializeAsync(user, token).ConfigureAwait(false);
// Should not raise propery changed event here
user.SetSelectedUserGameRole(user.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen), false);
return user.IsInitialized = true;
}

View File

@@ -300,8 +300,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.1">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -316,7 +316,7 @@
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.15.3">

View File

@@ -7,6 +7,7 @@
xmlns:cwconv="using:CommunityToolkit.WinUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcb="using:Snap.Hutao.Control.Brush"
xmlns:shch="using:Snap.Hutao.Control.Helper"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
@@ -112,7 +113,7 @@
<Expander
x:Name="DetailExpander"
Padding="16,0,16,0"
Padding="12,0,12,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Background="Transparent"
@@ -221,101 +222,172 @@
IsPredictPullAvailable="{Binding IsPredictPullAvailable}"
SelectedIndex="0"/>
<cwcont:SwitchPresenter
Height="85"
Height="96"
Padding="0,12"
Value="{x:Bind StatisticsSegmented.SelectedIndex, Mode=OneWay}">
<cwcont:Case Value="{shcm:Int32 Value=0}">
<StackPanel Spacing="2">
<Grid>
<TextBlock
HorizontalAlignment="Left"
Style="{StaticResource BodyTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeAveragePullText}"/>
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding AverageOrangePullFormatted}"/>
<Grid ColumnSpacing="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Style="{ThemeResource BorderCardStyle}">
<Viewbox Margin="8,0" StretchDirection="DownOnly">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock
Opacity="0.8"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeAveragePullText}"/>
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{Binding AverageOrangePullFormatted}"/>
</StackPanel>
</Viewbox>
</Border>
<Border Grid.Column="1" Style="{ThemeResource BorderCardStyle}">
<Viewbox Margin="8,0" StretchDirection="DownOnly">
<Grid>
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{x:Bind ShowUpPull, Converter={StaticResource BoolToVisibilityConverter}}">
<TextBlock
Opacity="0.8"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardUpAveragePullText}"/>
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{Binding AverageUpOrangePullFormatted}"/>
</StackPanel>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="N/A"
Visibility="{x:Bind ShowUpPull, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
</Grid>
</Viewbox>
</Border>
<Grid Grid.Column="2" RowSpacing="2">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Style="{ThemeResource BorderCardStyle}">
<Viewbox Margin="8,0" StretchDirection="DownOnly">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding MaxOrangePullFormatted}"/>
</Viewbox>
</Border>
<Border Grid.Row="1" Style="{ThemeResource BorderCardStyle}">
<Viewbox Margin="8,0" StretchDirection="DownOnly">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding MinOrangePullFormatted}"/>
</Viewbox>
</Border>
</Grid>
<Grid>
<!-- 高度占位符 -->
<TextBlock/>
<TextBlock
HorizontalAlignment="Left"
Style="{StaticResource BodyTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardUpAveragePullText}"
Visibility="{x:Bind ShowUpPull, Converter={StaticResource BoolToVisibilityConverter}}"/>
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding AverageUpOrangePullFormatted}"
Visibility="{x:Bind ShowUpPull, Converter={StaticResource BoolToVisibilityConverter}}"/>
</Grid>
<Grid>
<TextBlock
HorizontalAlignment="Left"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding MaxOrangePullFormatted}"/>
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding MinOrangePullFormatted}"/>
</Grid>
<Grid/>
</StackPanel>
</Grid>
</cwcont:Case>
<cwcont:Case Value="{shcm:Int32 Value=1}">
<StackPanel Spacing="2">
<Grid>
<TextBlock
HorizontalAlignment="Left"
Foreground="{StaticResource OrangeColorBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
<TextBlock
HorizontalAlignment="Right"
FontFamily="{StaticResource CascadiaMonoAndMiSans}"
Foreground="{StaticResource OrangeColorBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding TotalOrangeFormatted}"/>
<Grid RowSpacing="4">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid ColumnSpacing="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Style="{ThemeResource GridCardStyle}">
<StackPanel
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock
Foreground="{StaticResource OrangeColorBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
<Viewbox StretchDirection="DownOnly">
<TextBlock Foreground="{StaticResource OrangeColorBrush}" Text="{Binding TotalOrangeFormatted}"/>
</Viewbox>
</StackPanel>
</Grid>
<Grid Grid.Column="1" Style="{ThemeResource GridCardStyle}">
<StackPanel
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock
Foreground="{StaticResource PurpleColorBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
<Viewbox StretchDirection="DownOnly">
<TextBlock Foreground="{StaticResource PurpleColorBrush}" Text="{Binding TotalPurpleFormatted}"/>
</Viewbox>
</StackPanel>
</Grid>
<Grid Grid.Column="2" Style="{ThemeResource GridCardStyle}">
<StackPanel
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock
Foreground="{StaticResource BlueColorBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardBlueText}"/>
<Viewbox StretchDirection="DownOnly">
<TextBlock Foreground="{StaticResource BlueColorBrush}" Text="{Binding TotalBlueFormatted}"/>
</Viewbox>
</StackPanel>
</Grid>
</Grid>
<Grid>
<TextBlock
HorizontalAlignment="Left"
Foreground="{StaticResource PurpleColorBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
<TextBlock
HorizontalAlignment="Right"
FontFamily="{StaticResource CascadiaMonoAndMiSans}"
Foreground="{StaticResource PurpleColorBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding TotalPurpleFormatted}"/>
</Grid>
<Grid>
<TextBlock
HorizontalAlignment="Left"
Foreground="{StaticResource BlueColorBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardBlueText}"/>
<TextBlock
HorizontalAlignment="Right"
FontFamily="{StaticResource CascadiaMonoAndMiSans}"
Foreground="{StaticResource BlueColorBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding TotalBlueFormatted}"/>
</Grid>
</StackPanel>
<shcb:SegmentedBar
Grid.Row="1"
Height="2"
Margin="2,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Opacity="0.7"
Source="{Binding PullPercentSegmentSource}"/>
</Grid>
</cwcont:Case>
<cwcont:Case Value="{shcm:Int32 Value=2}">
<StackPanel Spacing="2">
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{Binding PredictedPullLeftToOrangeFormatted}"/>
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{Binding ProbabilityOfNextPullIsOrangeFormatted}"/>
</StackPanel>
<Grid RowSpacing="2">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Style="{ThemeResource BorderCardStyle}">
<Viewbox StretchDirection="DownOnly">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding PredictedPullLeftToOrangeFormatted}"/>
</Viewbox>
</Border>
<Border Grid.Row="1" Style="{ThemeResource BorderCardStyle}">
<Viewbox StretchDirection="DownOnly">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding ProbabilityOfNextPullIsOrangeFormatted}"/>
</Viewbox>
</Border>
</Grid>
</cwcont:Case>
</cwcont:SwitchPresenter>
<MenuFlyoutSeparator Margin="-12,0"/>
</StackPanel>
</Expander>
<cwcont:SwitchPresenter

View File

@@ -4,23 +4,36 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shch="using:Snap.Hutao.Control.Helper"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shv="using:Snap.Hutao.View"
xmlns:shvh="using:Snap.Hutao.View.Helper"
xmlns:shvm="using:Snap.Hutao.ViewModel"
xmlns:shvp="using:Snap.Hutao.View.Page"
d:DataContext="{d:DesignInstance Type=shvm:MainViewModel}"
mc:Ignorable="d">
<mxi:Interaction.Behaviors>
<shcb:PeriodicInvokeCommandOrOnActualThemeChangedBehavior
Command="{Binding UpdateBackgroundCommand}"
CommandParameter="{x:Bind BackgroundImagePresenter}"
Period="0:5:0"/>
</mxi:Interaction.Behaviors>
<UserControl.Resources>
<Thickness x:Key="NavigationViewContentMargin">0,44,0,0</Thickness>
<Thickness x:Key="NavigationViewContentGridBorderThickness">0,1,0,0</Thickness>
<x:Double x:Key="NavigationViewItemOnLeftIconBoxHeight">24</x:Double>
<SolidColorBrush x:Key="NavigationViewExpandedPaneBackground" Color="Transparent"/>
</UserControl.Resources>
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}" Transitions="{ThemeResource EntranceThemeTransitions}">
<!-- Background="{ThemeResource SolidBackgroundFillColorBaseBrush}" -->
<Grid Transitions="{ThemeResource EntranceThemeTransitions}">
<Image
x:Name="BackdroundImagePresenter"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
x:Name="BackgroundImagePresenter"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Opacity="0"
Stretch="UniformToFill"/>

View File

@@ -5,10 +5,12 @@ using CommunityToolkit.WinUI.Animations;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Snap.Hutao.Control.Animation;
using Snap.Hutao.Control.Theme;
using Snap.Hutao.Service.BackgroundImage;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Page;
using Snap.Hutao.ViewModel;
namespace Snap.Hutao.View;
@@ -19,80 +21,50 @@ namespace Snap.Hutao.View;
internal sealed partial class MainView : UserControl
{
private readonly INavigationService navigationService;
private readonly IBackgroundImageService backgroundImageService;
private TaskCompletionSource acutalThemeChangedTaskCompletionSource = new();
private CancellationTokenSource periodicTimerCancellationTokenSource = new();
private BackgroundImage? previousBackgroundImage;
/// <summary>
/// 构造一个新的主视图
/// </summary>
public MainView()
{
InitializeComponent();
ActualThemeChanged += OnActualThemeChanged;
IServiceProvider serviceProvider = Ioc.Default;
backgroundImageService = serviceProvider.GetRequiredService<IBackgroundImageService>();
RunBackgroundImageLoopAsync(serviceProvider.GetRequiredService<ITaskContext>()).SafeForget();
MainViewModel mainViewModel = serviceProvider.GetRequiredService<MainViewModel>();
DataContext = mainViewModel;
InitializeComponent();
mainViewModel.Initialize(new BackgroundImagePresenterAccessor(BackgroundImagePresenter));
navigationService = serviceProvider.GetRequiredService<INavigationService>();
navigationService
.As<INavigationInitialization>()?
.Initialize(NavView, ContentFrame);
if (navigationService is INavigationInitialization navigationInitialization)
{
navigationInitialization.Initialize(new NavigationViewAccessor(NavView, ContentFrame));
}
navigationService.Navigate<AnnouncementPage>(INavigationAwaiter.Default, true);
}
private async ValueTask RunBackgroundImageLoopAsync(ITaskContext taskContext)
private class NavigationViewAccessor : INavigationViewAccessor
{
using (PeriodicTimer timer = new(TimeSpan.FromMinutes(5)))
public NavigationViewAccessor(NavigationView navigationView, Frame frame)
{
do
{
(bool isOk, BackgroundImage backgroundImage) = await backgroundImageService.GetNextBackgroundImageAsync(previousBackgroundImage).ConfigureAwait(false);
if (isOk)
{
previousBackgroundImage = backgroundImage;
await taskContext.SwitchToMainThreadAsync();
await AnimationBuilder
.Create()
.Opacity(to: 0D, duration: TimeSpan.FromMilliseconds(1000), easingType: EasingType.Sine, easingMode: EasingMode.EaseIn)
.StartAsync(BackdroundImagePresenter)
.ConfigureAwait(true);
BackdroundImagePresenter.Source = backgroundImage.ImageSource;
double targetOpacity = ThemeHelper.IsDarkMode(ActualTheme) ? 1 - backgroundImage.Luminance : backgroundImage.Luminance;
await AnimationBuilder
.Create()
.Opacity(to: targetOpacity, duration: TimeSpan.FromMilliseconds(1000), easingType: EasingType.Sine, easingMode: EasingMode.EaseOut)
.StartAsync(BackdroundImagePresenter)
.ConfigureAwait(true);
}
try
{
await Task.WhenAny(timer.WaitForNextTickAsync(periodicTimerCancellationTokenSource.Token).AsTask(), acutalThemeChangedTaskCompletionSource.Task).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
acutalThemeChangedTaskCompletionSource = new();
periodicTimerCancellationTokenSource = new();
}
while (true);
NavigationView = navigationView;
Frame = frame;
}
public NavigationView NavigationView { get; private set; }
public Frame Frame { get; private set; }
}
private void OnActualThemeChanged(FrameworkElement frameworkElement, object args)
private class BackgroundImagePresenterAccessor : IBackgroundImagePresenterAccessor
{
acutalThemeChangedTaskCompletionSource.TrySetResult();
periodicTimerCancellationTokenSource.Cancel();
public BackgroundImagePresenterAccessor(Image backgroundImagePresenter)
{
BackgroundImagePresenter = backgroundImagePresenter;
}
public Image BackgroundImagePresenter { get; private set; }
}
}

View File

@@ -149,20 +149,22 @@
</DataTemplate>
<DataTemplate x:Key="AnnouncementPivotItemContentTemplate">
<ItemsRepeater
Margin="16,16,16,16"
HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource AnnouncementTemplate}"
ItemsSource="{Binding List}">
<ItemsRepeater.Layout>
<UniformGridLayout
ItemsJustification="Start"
ItemsStretch="Fill"
MinColumnSpacing="12"
MinItemWidth="300"
MinRowSpacing="12"/>
</ItemsRepeater.Layout>
</ItemsRepeater>
<ScrollViewer>
<ItemsRepeater
Margin="16,16,16,16"
HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource AnnouncementTemplate}"
ItemsSource="{Binding List}">
<ItemsRepeater.Layout>
<UniformGridLayout
ItemsJustification="Start"
ItemsStretch="Fill"
MinColumnSpacing="12"
MinItemWidth="300"
MinRowSpacing="12"/>
</ItemsRepeater.Layout>
</ItemsRepeater>
</ScrollViewer>
</DataTemplate>
<DataTemplate x:Key="HutaoAnnouncementTemplate">

View File

@@ -346,6 +346,12 @@
HeaderIcon="{shcm:FontIcon Glyph=&#xEC92;}">
<ToggleSwitch MinWidth="{ThemeResource SettingsCardContentControlMinWidth}" IsOn="{Binding LaunchOptions.UseStarwardPlayTimeStatistics, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGameBetterGIDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameBetterGIHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE961;}">
<ToggleSwitch MinWidth="{ThemeResource SettingsCardContentControlMinWidth}" IsOn="{Binding LaunchOptions.UseBetterGenshinImpactAutomation, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGameDiscordActivityDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameDiscordActivityHeader}"

View File

@@ -3,10 +3,12 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cw="using:CommunityToolkit.WinUI"
xmlns:cwb="using:CommunityToolkit.WinUI.Behaviors"
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shch="using:Snap.Hutao.Control.Helper"
@@ -312,6 +314,92 @@
SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/>
</shc:SizeRestrictedContentControl>
</cwc:SettingsCard>
<cwc:SettingsExpander
Description="{shcm:ResourceString Name=ViewPageSettingBackgroundImageDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingBackgroundImageHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE8B9;}"
IsExpanded="{Binding BackgroundImageOptions.Wallpaper, Converter={StaticResource EmptyObjectToBoolConverter}}">
<shc:SizeRestrictedContentControl VerticalAlignment="Center">
<ComboBox
DisplayMemberPath="Name"
ItemsSource="{Binding AppOptions.BackgroundImageTypes}"
SelectedItem="{Binding SelectedBackgroundImageType, Mode=TwoWay}"/>
</shc:SizeRestrictedContentControl>
<cwc:SettingsExpander.Items>
<cwc:SettingsCard
Description="{Binding BackgroundImageOptions.Wallpaper.Author}"
Header="{shcm:ResourceString Name=ViewPageSettingBackgroundImageCopyrightHeader}"
IsClickEnabled="True"
Visibility="{Binding BackgroundImageOptions.Wallpaper, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="Click">
<cwb:NavigateToUriAction NavigateUri="{Binding BackgroundImageOptions.Wallpaper.SourceUrl}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</cwc:SettingsCard>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
</StackPanel>
</Border>
</Border>
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingStorageHeader}"/>
<cwc:SettingsExpander
Description="{Binding DataFolderView.Size}"
Header="{shcm:ResourceString Name=ViewPageSettingDataFolderHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xEC25;}"
IsExpanded="True">
<cwc:SettingsExpander.Content>
<Button
Command="{Binding DataFolderView.OpenFolderCommand}"
Content="{shcm:ResourceString Name=ViewSettingFolderViewOpenFolderAction}"
Style="{ThemeResource SettingButtonStyle}"/>
</cwc:SettingsExpander.Content>
<cwc:SettingsExpander.Items>
<cwc:SettingsCard
Command="{Binding OpenBackgroundImageFolderCommand}"
Description="{shcm:ResourceString Name=ViewPageSettingOpenBackgroundImageFolderDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingOpenBackgroundImageFolderHeader}"
IsClickEnabled="True"/>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingStorageSetAction}"
Command="{Binding SetDataFolderCommand}"
Description="{shcm:ResourceString Name=ViewPageSettingSetDataFolderDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingSetDataFolderHeader}"
IsClickEnabled="True"/>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
Command="{Binding DeleteServerCacheFolderCommand}"
Description="{shcm:ResourceString Name=ViewSettingDeleteServerCacheFolderDescription}"
Header="{shcm:ResourceString Name=ViewSettingDeleteServerCacheFolderHeader}"
IsClickEnabled="True"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
<cwc:SettingsExpander
Description="{Binding CacheFolderView.Size}"
Header="{shcm:ResourceString Name=ViewPageSettingCacheFolderHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE8B7;}"
IsExpanded="True">
<cwc:SettingsExpander.Content>
<Button
Command="{Binding CacheFolderView.OpenFolderCommand}"
Content="{shcm:ResourceString Name=ViewSettingFolderViewOpenFolderAction}"
Style="{ThemeResource SettingButtonStyle}"/>
</cwc:SettingsExpander.Content>
<cwc:SettingsExpander.Items>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingResetAction}"
Command="{Binding ResetStaticResourceCommand}"
Description="{shcm:ResourceString Name=ViewPageSettingResetStaticResourceDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingResetStaticResourceHeader}"
IsClickEnabled="True"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
</StackPanel>
</Border>
</Border>
@@ -466,67 +554,6 @@
</Border>
</Border>
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingStorageHeader}"/>
<cwc:SettingsExpander
Description="{Binding DataFolderView.Size}"
Header="{shcm:ResourceString Name=ViewPageSettingDataFolderHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xEC25;}"
IsExpanded="True">
<cwc:SettingsExpander.Content>
<Button
Command="{Binding DataFolderView.OpenFolderCommand}"
Content="{shcm:ResourceString Name=ViewSettingFolderViewOpenFolderAction}"
Style="{ThemeResource SettingButtonStyle}"/>
</cwc:SettingsExpander.Content>
<cwc:SettingsExpander.Items>
<cwc:SettingsCard
Command="{Binding OpenBackgroundImageFolderCommand}"
Description="{shcm:ResourceString Name=ViewPageSettingOpenBackgroundImageFolderDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingOpenBackgroundImageFolderHeader}"
IsClickEnabled="True"/>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingStorageSetAction}"
Command="{Binding SetDataFolderCommand}"
Description="{shcm:ResourceString Name=ViewPageSettingSetDataFolderDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingSetDataFolderHeader}"
IsClickEnabled="True"/>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
Command="{Binding DeleteServerCacheFolderCommand}"
Description="{shcm:ResourceString Name=ViewSettingDeleteServerCacheFolderDescription}"
Header="{shcm:ResourceString Name=ViewSettingDeleteServerCacheFolderHeader}"
IsClickEnabled="True"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
<cwc:SettingsExpander
Description="{Binding CacheFolderView.Size}"
Header="{shcm:ResourceString Name=ViewPageSettingCacheFolderHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE8B7;}"
IsExpanded="True">
<cwc:SettingsExpander.Content>
<Button
Command="{Binding CacheFolderView.OpenFolderCommand}"
Content="{shcm:ResourceString Name=ViewSettingFolderViewOpenFolderAction}"
Style="{ThemeResource SettingButtonStyle}"/>
</cwc:SettingsExpander.Content>
<cwc:SettingsExpander.Items>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingResetAction}"
Command="{Binding ResetStaticResourceCommand}"
Description="{shcm:ResourceString Name=ViewPageSettingResetStaticResourceDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingResetStaticResourceHeader}"
IsClickEnabled="True"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
</StackPanel>
</Border>
</Border>
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">

View File

@@ -221,7 +221,7 @@
<shct:DescriptionTextBlock
Margin="0,8,0,16"
Description="{Binding Description}"
Style="{StaticResource CaptionTextBlockStyle}"/>
TextStyle="{StaticResource CaptionTextBlockStyle}"/>
</StackPanel>
</Grid>
</DataTemplate>

View File

@@ -291,7 +291,7 @@
ProfilePicture="{Binding UserInfo.AvatarUrl, Mode=OneWay}"/>
<TextBlock
Grid.Column="1"
Margin="10,0,0,0"
Margin="12,0,0,0"
VerticalAlignment="Center"
Text="{Binding UserInfo.Nickname}"/>
<TextBlock

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Control.Brush;
using Snap.Hutao.Control.Theme;
namespace Snap.Hutao.ViewModel.GachaLog;
@@ -83,6 +85,16 @@ internal sealed partial class TypedWishSummary : Wish
get => $"{TotalBluePull} [{TotalBluePercent,6:p2}]";
}
public ColorSegmentCollection PullPercentSegmentSource
{
get =>
[
new ColorSegment(KnownColors.Orange, TotalOrangePull),
new ColorSegment(KnownColors.Purple, TotalPurplePull),
new ColorSegment(KnownColors.Blue, TotalBluePull),
];
}
/// <summary>
/// 平均五星抽数
/// </summary>

View File

@@ -5,6 +5,7 @@ using CommunityToolkit.Common;
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO;
using System.IO;
using System.IO.Compression;
@@ -74,6 +75,14 @@ internal sealed class DownloadSummary : ObservableObject
try
{
HttpResponseMessage response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
if (response.Content.Headers.ContentType?.MediaType is not "application/octet-stream")
{
logger.LogWarning("Download Static Zip failed, Content-Type is {Type}", response.Content.Headers.ContentType);
Description = SH.ViewModelWelcomeDownloadSummaryContentTypeNotMatch;
return false;
}
long contentLength = response.Content.Headers.ContentLength ?? 0;
logger.LogInformation("Begin download, length: {length}", contentLength);
using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
@@ -95,7 +104,7 @@ internal sealed class DownloadSummary : ObservableObject
logger.LogError(ex, "Download Static Zip failed");
await taskContext.SwitchToMainThreadAsync();
Description = ex is HttpRequestException httpRequestException
? $"{SH.ViewModelWelcomeDownloadSummaryException} - HTTP {httpRequestException.HttpRequestError} {httpRequestException.StatusCode:D}"
? $"{SH.ViewModelWelcomeDownloadSummaryException} - [HTTP '{httpRequestException.StatusCode:D}'] [Error '{httpRequestException.HttpRequestError}']"
: ex.Message;
return false;
}
@@ -109,8 +118,10 @@ internal sealed class DownloadSummary : ObservableObject
private void ExtractFiles(Stream stream)
{
IImageCacheFilePathOperation? imageCacheFilePathOperation = imageCache.As<IImageCacheFilePathOperation>();
ArgumentNullException.ThrowIfNull(imageCacheFilePathOperation);
if (imageCache is not IImageCacheFilePathOperation imageCacheFilePathOperation)
{
throw HutaoException.ServiceTypeCastFailed<IImageCache, IImageCacheFilePathOperation>(nameof(imageCache));
}
using (ZipArchive archive = new(stream))
{

View File

@@ -0,0 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control;
namespace Snap.Hutao.ViewModel;
internal interface IBackgroundImagePresenterAccessor : IXamlElementAccessor
{
Image BackgroundImagePresenter { get; }
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel;
internal interface IMainViewModelInitialization
{
void Initialize(IBackgroundImagePresenterAccessor accessor);
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Animations;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Snap.Hutao.Control.Animation;
using Snap.Hutao.Control.Theme;
using Snap.Hutao.Message;
using Snap.Hutao.Service.BackgroundImage;
namespace Snap.Hutao.ViewModel;
[ConstructorGenerated]
[Injection(InjectAs.Singleton)]
internal sealed partial class MainViewModel : Abstraction.ViewModel, IMainViewModelInitialization, IRecipient<BackgroundImageTypeChangedMessage>
{
private readonly IBackgroundImageService backgroundImageService;
private readonly ITaskContext taskContext;
private BackgroundImage? previousBackgroundImage;
private Image? backgroundImagePresenter;
public void Initialize(IBackgroundImagePresenterAccessor accessor)
{
backgroundImagePresenter = accessor.BackgroundImagePresenter;
}
public void Receive(BackgroundImageTypeChangedMessage message)
{
UpdateBackgroundAsync().SafeForget();
}
[Command("UpdateBackgroundCommand")]
private async Task UpdateBackgroundAsync()
{
if (backgroundImagePresenter is null)
{
return;
}
(bool isOk, BackgroundImage backgroundImage) = await backgroundImageService.GetNextBackgroundImageAsync(previousBackgroundImage).ConfigureAwait(false);
if (isOk)
{
previousBackgroundImage = backgroundImage;
await taskContext.SwitchToMainThreadAsync();
await AnimationBuilder
.Create()
.Opacity(
to: 0D,
duration: ControlAnimationConstants.ImageOpacityFadeInOut,
easingType: EasingType.Quartic,
easingMode: EasingMode.EaseInOut)
.StartAsync(backgroundImagePresenter)
.ConfigureAwait(true);
backgroundImagePresenter.Source = backgroundImage.ImageSource;
double targetOpacity = ThemeHelper.IsDarkMode(backgroundImagePresenter.ActualTheme) ? 1 - backgroundImage.Luminance : backgroundImage.Luminance;
await AnimationBuilder
.Create()
.Opacity(
to: targetOpacity,
duration: ControlAnimationConstants.ImageOpacityFadeInOut,
easingType: EasingType.Quartic,
easingMode: EasingMode.EaseInOut)
.StartAsync(backgroundImagePresenter)
.ConfigureAwait(true);
}
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
@@ -12,6 +13,7 @@ using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Factory.Picker;
using Snap.Hutao.Model;
using Snap.Hutao.Service;
using Snap.Hutao.Service.BackgroundImage;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Hutao;
@@ -40,6 +42,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
private readonly HutaoPassportViewModel hutaoPassportViewModel;
private readonly BackgroundImageOptions backgroundImageOptions;
private readonly IContentDialogFactory contentDialogFactory;
private readonly INavigationService navigationService;
private readonly IShellLinkInterop shellLinkInterop;
@@ -52,8 +55,10 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
private readonly IUserService userService;
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
private readonly IMessenger messenger;
private NameValue<BackdropType>? selectedBackdropType;
private NameValue<BackgroundImageType>? selectedBackgroundImageType;
private NameValue<CultureInfo>? selectedCulture;
private NameValue<Region>? selectedRegion;
private FolderViewModel? cacheFolderView;
@@ -73,6 +78,8 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
public LaunchOptions LaunchOptions { get => launchOptions; }
public BackgroundImageOptions BackgroundImageOptions { get => backgroundImageOptions; }
public HutaoPassportViewModel Passport { get => hutaoPassportViewModel; }
public NameValue<BackdropType>? SelectedBackdropType
@@ -87,6 +94,19 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
}
}
public NameValue<BackgroundImageType>? SelectedBackgroundImageType
{
get => selectedBackgroundImageType ??= AppOptions.BackgroundImageTypes.Single(t => t.Value == AppOptions.BackgroundImageType);
set
{
if (SetProperty(ref selectedBackgroundImageType, value) && value is not null)
{
AppOptions.BackgroundImageType = value.Value;
messenger.Send(new Message.BackgroundImageTypeChangedMessage());
}
}
}
public NameValue<CultureInfo>? SelectedCulture
{
get => selectedCulture ??= CultureOptions.GetCurrentCultureForSelectionOrDefault();
@@ -167,13 +187,13 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
if (await dialog.ConfirmAsync(SH.ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader).ConfigureAwait(true))
{
launchOptions.IsAdvancedLaunchOptionsEnabled = true;
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
OnPropertyChanged(nameof(IsAdvancedLaunchOptionsEnabled));
return;
}
}
launchOptions.IsAdvancedLaunchOptionsEnabled = false;
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
OnPropertyChanged(nameof(IsAdvancedLaunchOptionsEnabled));
}
}
}

View File

@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
@@ -20,18 +21,14 @@ namespace Snap.Hutao.ViewModel.User;
internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, IMappingFrom<User, EntityUser, IServiceProvider>, ISelectable
{
private readonly EntityUser inner;
private readonly IMessenger messenger;
private readonly IServiceProvider serviceProvider;
private UserGameRole? selectedUserGameRole;
/// <summary>
/// 构造一个新的绑定视图用户
/// </summary>
/// <param name="user">用户实体</param>
private User(EntityUser user, IServiceProvider serviceProvider)
{
inner = user;
messenger = serviceProvider.GetRequiredService<IMessenger>();
this.serviceProvider = serviceProvider;
}
public bool IsInitialized { get; set; }
@@ -99,6 +96,8 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, IMapping
public bool NeedDbUpdateAfterResume { get; set; }
public string? PreferredUid { get => inner.PreferredUid; }
public static User From(EntityUser user, IServiceProvider provider)
{
return new(user, provider);
@@ -106,9 +105,21 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, IMapping
public void SetSelectedUserGameRole(UserGameRole? value, bool raiseMessage = true)
{
if (SetProperty(ref selectedUserGameRole, value, nameof(SelectedUserGameRole)) && raiseMessage)
if (SetProperty(ref selectedUserGameRole, value, nameof(SelectedUserGameRole)))
{
messenger.Send(Message.UserChangedMessage.CreateOnlyRoleChanged(this));
if (value is not null && inner.PreferredUid != value.GameUid)
{
inner.PreferredUid = value.GameUid;
using (IServiceScope scope = serviceProvider.CreateScope())
{
scope.ServiceProvider.GetRequiredService<AppDbContext>().Users.UpdateAndSave(inner);
}
}
if (raiseMessage)
{
serviceProvider.GetRequiredService<IMessenger>().Send(Message.UserChangedMessage.CreateOnlyRoleChanged(this));
}
}
}
}

View File

@@ -53,10 +53,18 @@ internal sealed partial class UserViewModel : ObservableObject
get => selectedUser ??= userService.Current;
set
{
if (value is { SelectedUserGameRole: null })
if (value is not null)
{
// Pre select the chosen role to avoid multiple UserChangedMessage
value.SetSelectedUserGameRole(value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen), false);
// Should not raise propery changed event below
if (value.PreferredUid is not null)
{
value.SetSelectedUserGameRole(value.UserGameRoles.FirstOrDefault(role => role.GameUid == value.PreferredUid), false);
}
if (value.SelectedUserGameRole is null)
{
value.SetSelectedUserGameRole(value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen), false);
}
}
if (SetProperty(ref selectedUser, value))

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Web;
namespace Snap.Hutao.Web.Hutao.Algolia;
internal sealed class AlgoliaHierarchy
@@ -35,42 +37,42 @@ internal sealed class AlgoliaHierarchy
yield break;
}
yield return Lvl1;
yield return HttpUtility.HtmlDecode(Lvl1);
if (string.IsNullOrEmpty(Lvl2))
{
yield break;
}
yield return Lvl2;
yield return HttpUtility.HtmlDecode(Lvl2);
if (string.IsNullOrEmpty(Lvl3))
{
yield break;
}
yield return Lvl3;
yield return HttpUtility.HtmlDecode(Lvl3);
if (string.IsNullOrEmpty(Lvl4))
{
yield break;
}
yield return Lvl4;
yield return HttpUtility.HtmlDecode(Lvl4);
if (string.IsNullOrEmpty(Lvl5))
{
yield break;
}
yield return Lvl5;
yield return HttpUtility.HtmlDecode(Lvl5);
if (string.IsNullOrEmpty(Lvl6))
{
yield break;
}
yield return Lvl6;
yield return HttpUtility.HtmlDecode(Lvl6);
}
}
}

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