Compare commits

..

20 Commits

Author SHA1 Message Date
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
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
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
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
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
53 changed files with 1210 additions and 251 deletions

View File

@@ -13,6 +13,15 @@ on:
- '**.md'
- 'LICENSE'
- '**.yml'
pull_request:
paths-ignore:
- '.gitattributes'
- '.github/**'
- '.gitignore'
- '.gitmodules'
- '**.md'
- 'LICENSE'
- '**.yml'
jobs:
build:
@@ -34,20 +43,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

@@ -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

@@ -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

@@ -5,40 +5,53 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using System.Collections.Specialized;
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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -824,6 +824,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>
@@ -2189,6 +2192,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>

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,14 @@ 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 BackgroundImageType BackgroundImageType
{
get => GetOption(ref backgroundImageType, SettingEntry.BackgroundImageType, EnumParse<BackgroundImageType>, BackgroundImageType.HutaoOfficialLauncher).Value;
set => SetOption(ref backgroundImageType, SettingEntry.BackgroundImageType, value, EnumToStringOrEmpty);
}
public Lazy<List<NameValue<Region>>> LazyRegions { get; } = new(KnownRegions.Get);
@@ -45,4 +53,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

@@ -13,6 +13,8 @@ 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

@@ -38,8 +38,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
string path = System.Random.Shared.GetItems(backgroundSet.ToArray(), 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!);
}
@@ -54,6 +53,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
BackgroundImage background = new()
{
Path = path,
ImageSource = new(path.ToUri()),
AccentColor = accentColor,
Luminance = accentColor.Luminance,
@@ -65,7 +65,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
private async ValueTask<HashSet<string>> SkipOrInitBackgroundAsync()
{
if (backgroundPathSet is null || backgroundPathSet.Count <= 0)
if (backgroundPathSet is not { Count: > 0 })
{
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
Directory.CreateDirectory(backgroundFolder);

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.BackgroundImage;
internal enum BackgroundImageType
{
None,
LocalFolder,
HutaoBing,
HutaoDaily,
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

@@ -3,6 +3,7 @@
using Snap.Hutao.Core;
using Snap.Hutao.Service.Notification;
using System.Diagnostics;
namespace Snap.Hutao.Service.Discord;
@@ -17,7 +18,7 @@ internal sealed partial class DiscordService : IDiscordService, IDisposable
public async ValueTask SetPlayingActivityAsync(bool isOversea)
{
if (CheckDiscordStatus())
if (IsSupported())
{
_ = isOversea
? await DiscordController.SetPlayingGenshinImpactAsync().ConfigureAwait(false)
@@ -27,7 +28,7 @@ internal sealed partial class DiscordService : IDiscordService, IDisposable
public async ValueTask SetNormalActivityAsync()
{
if (CheckDiscordStatus())
if (IsSupported())
{
_ = await DiscordController.SetDefaultActivityAsync(runtimeOptions.AppLaunchTime).ConfigureAwait(false);
}
@@ -38,35 +39,30 @@ internal sealed partial class DiscordService : IDiscordService, IDisposable
DiscordController.Stop();
}
private bool CheckDiscordStatus()
private bool IsSupported()
{
try
{
// Actually requires a discord client to be running on Windows platform.
// If not, discord core creation code will throw.
System.Diagnostics.Process[] discordProcesses = System.Diagnostics.Process.GetProcessesByName("Discord");
Process[] discordProcesses = Process.GetProcessesByName("Discord");
if (discordProcesses.Length <= 0)
{
if (!isInitialized)
{
infoBarService.Warning("Discord 未运行,将无法设置 Discord Activity 状态。");
}
return false;
}
foreach (System.Diagnostics.Process process in discordProcesses)
foreach (Process process in discordProcesses)
{
try
{
_ = process.Handle;
}
catch (Win32Exception)
catch (Exception)
{
if (!isInitialized)
{
infoBarService.Warning("权限不足,将无法设置 Discord Activity 状态。");
infoBarService.Warning(SH.ServiceDiscordActivityElevationRequiredHint);
}
return false;
@@ -77,10 +73,7 @@ internal sealed partial class DiscordService : IDiscordService, IDisposable
}
finally
{
if (!isInitialized)
{
isInitialized = true;
}
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)
@@ -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

@@ -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

@@ -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"
@@ -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

@@ -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

@@ -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

@@ -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);
}
}
}

View File

@@ -0,0 +1,44 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Web.Request.Builder;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using Snap.Hutao.Web.Response;
using System.Net.Http;
namespace Snap.Hutao.Web.Hutao.Wallpaper;
[HttpClient(HttpClientConfiguration.Default)]
[ConstructorGenerated(ResolveHttpClient = true)]
internal sealed partial class HutaoWallpaperClient
{
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly ILogger<HutaoWallpaperClient> logger;
private readonly HttpClient httpClient;
public ValueTask<Response<Wallpaper>> GetBingWallpaperAsync(CancellationToken token = default)
{
return GetWallpaperAsync(HutaoEndpoints.WallpaperBing, token);
}
public ValueTask<Response<Wallpaper>> GetLauncherWallpaperAsync(CancellationToken token = default)
{
return GetWallpaperAsync(HutaoEndpoints.WallpaperGenshinLauncher, token);
}
public ValueTask<Response<Wallpaper>> GetTodayWallpaperAsync(CancellationToken token = default)
{
return GetWallpaperAsync(HutaoEndpoints.WallpaperToday, token);
}
private async ValueTask<Response<Wallpaper>> GetWallpaperAsync(string url, CancellationToken token = default)
{
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(url)
.Get();
Response<Wallpaper>? resp = await builder.TryCatchSendAsync<Response<Wallpaper>>(httpClient, logger, token).ConfigureAwait(false);
return Web.Response.Response.DefaultIfNull(resp);
}
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hutao.Wallpaper;
internal sealed class Wallpaper
{
[JsonPropertyName("url")]
public string Url { get; set; } = default!;
[JsonPropertyName("source_url")]
public string SourceUrl { get; set; } = default!;
[JsonPropertyName("author")]
public string Author { get; set; } = default!;
[JsonPropertyName("uploader")]
public string Uploader { get; set; } = default!;
}

View File

@@ -10,8 +10,9 @@ namespace Snap.Hutao.Web;
/// 胡桃 API 端点
/// </summary>
[HighQuality]
[SuppressMessage("", "SA1201")]
[SuppressMessage("", "SA1124")]
[SuppressMessage("", "SA1201")]
[SuppressMessage("", "SA1203")]
internal static class HutaoEndpoints
{
#region HomaAPI
@@ -271,6 +272,15 @@ internal static class HutaoEndpoints
}
#endregion
#region Wallpaper
public const string WallpaperBing = $"{ApiSnapGenshin}/wallpaper/bing";
public const string WallpaperGenshinLauncher = $"{ApiSnapGenshin}/wallpaper/genshin-launcher";
public const string WallpaperToday = $"{ApiSnapGenshin}/wallpaper/today";
#endregion
#endregion
private const string ApiSnapGenshin = "https://api.snapgenshin.com";