mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
1 Commits
feat/unhel
...
fix/chroni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
544a2d7c6a |
@@ -110,6 +110,7 @@ dotnet_diagnostic.SA1642.severity = none
|
||||
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
dotnet_diagnostic.IDE0060.severity = none
|
||||
dotnet_diagnostic.IDE0290.severity = none
|
||||
|
||||
# SA1208: System using directives should be placed before other using directives
|
||||
dotnet_diagnostic.SA1208.severity = none
|
||||
@@ -320,8 +321,7 @@ dotnet_diagnostic.CA2227.severity = suggestion
|
||||
|
||||
# CA2251: 使用 “string.Equals”
|
||||
dotnet_diagnostic.CA2251.severity = suggestion
|
||||
|
||||
csharp_style_prefer_primary_constructors = false:none
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -10,6 +10,8 @@ using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Shell;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using static Snap.Hutao.Core.Logging.ConsoleVirtualTerminalSequences;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Collections.Specialized;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Foundation;
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
@@ -5,9 +5,6 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.IO.Hashing;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.IO;
|
||||
@@ -39,7 +36,6 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
||||
|
||||
private readonly IHttpClientFactory httpClientFactory;
|
||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ILogger<ImageCache> logger;
|
||||
|
||||
@@ -173,49 +169,38 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
|
||||
while (retryCount < 3)
|
||||
{
|
||||
HttpRequestMessageBuilder requestMessageBuilder = httpRequestMessageBuilderFactory
|
||||
.Create()
|
||||
.SetRequestUri(uri)
|
||||
|
||||
// These headers are only available for our own api
|
||||
.SetStaticResourceControlHeadersIf(uri.Host.Contains("api.snapgenshin.com", StringComparison.OrdinalIgnoreCase))
|
||||
.Get();
|
||||
|
||||
using (HttpRequestMessage requestMessage = requestMessageBuilder.HttpRequestMessage)
|
||||
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
using (HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
if (message.RequestMessage is { RequestUri: { } target } && target != uri)
|
||||
{
|
||||
if (responseMessage.RequestMessage is { RequestUri: { } target } && target != uri)
|
||||
{
|
||||
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
|
||||
}
|
||||
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
|
||||
}
|
||||
|
||||
if (responseMessage.IsSuccessStatusCode)
|
||||
if (message.IsSuccessStatusCode)
|
||||
{
|
||||
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
using (Stream httpStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
using (FileStream fileStream = File.Create(baseFile))
|
||||
{
|
||||
using (FileStream fileStream = File.Create(baseFile))
|
||||
{
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (responseMessage.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = responseMessage.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
|
||||
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
switch (message.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
|
||||
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Collection;
|
||||
|
||||
internal sealed class TwoEnumerbleEnumerator<TFirst, TSecond> : IDisposable
|
||||
{
|
||||
private readonly IEnumerator<TFirst> firstEnumerator;
|
||||
private readonly IEnumerator<TSecond> secondEnumerator;
|
||||
|
||||
public TwoEnumerbleEnumerator(IEnumerable<TFirst> firstEnumerable, IEnumerable<TSecond> secondEnumerable)
|
||||
{
|
||||
firstEnumerator = firstEnumerable.GetEnumerator();
|
||||
secondEnumerator = secondEnumerable.GetEnumerator();
|
||||
}
|
||||
|
||||
public (TFirst First, TSecond Second) Current { get => (firstEnumerator.Current, secondEnumerator.Current); }
|
||||
|
||||
public bool MoveNext(ref bool moveFirst, ref bool moveSecond)
|
||||
{
|
||||
moveFirst = moveFirst && firstEnumerator.MoveNext();
|
||||
moveSecond = moveSecond && secondEnumerator.MoveNext();
|
||||
|
||||
return moveFirst || moveSecond;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
firstEnumerator.Dispose();
|
||||
secondEnumerator.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,13 @@ namespace Snap.Hutao.Core.Database;
|
||||
[HighQuality]
|
||||
internal static class DbSetExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int AddAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -20,13 +27,27 @@ internal static class DbSetExtension
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
public static ValueTask<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, CancellationToken token = default)
|
||||
/// <summary>
|
||||
/// 异步添加并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static ValueTask<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Add(entity);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加列表并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entities">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int AddRangeAndSave<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -34,13 +55,27 @@ internal static class DbSetExtension
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
public static ValueTask<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities, CancellationToken token = default)
|
||||
/// <summary>
|
||||
/// 异步添加列表并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entities">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static ValueTask<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.AddRange(entities);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int RemoveAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -48,13 +83,27 @@ internal static class DbSetExtension
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
public static ValueTask<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, CancellationToken token = default)
|
||||
/// <summary>
|
||||
/// 异步移除并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static ValueTask<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Remove(entity);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static int UpdateAndSave<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -62,11 +111,18 @@ internal static class DbSetExtension
|
||||
return dbSet.SaveChangesAndClearChangeTracker();
|
||||
}
|
||||
|
||||
public static ValueTask<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, CancellationToken token = default)
|
||||
/// <summary>
|
||||
/// 异步更新并保存
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
/// <param name="dbSet">数据库集</param>
|
||||
/// <param name="entity">实体</param>
|
||||
/// <returns>影响条数</returns>
|
||||
public static ValueTask<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
dbSet.Update(entity);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync(token);
|
||||
return dbSet.SaveChangesAndClearChangeTrackerAsync();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -80,11 +136,11 @@ internal static class DbSetExtension
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static async ValueTask<int> SaveChangesAndClearChangeTrackerAsync<TEntity>(this DbSet<TEntity> dbSet, CancellationToken token = default)
|
||||
private static async ValueTask<int> SaveChangesAndClearChangeTrackerAsync<TEntity>(this DbSet<TEntity> dbSet)
|
||||
where TEntity : class
|
||||
{
|
||||
DbContext dbContext = dbSet.Context();
|
||||
int count = await dbContext.SaveChangesAsync(token).ConfigureAwait(false);
|
||||
int count = await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
dbContext.ChangeTracker.Clear();
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -47,12 +47,6 @@ internal sealed class HutaoException : Exception
|
||||
throw new HutaoException(HutaoExceptionKind.GachaStatisticsInvalidItemId, message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static HutaoException UserdataCorrupted(string message, Exception? innerException = default)
|
||||
{
|
||||
throw new HutaoException(HutaoExceptionKind.UserdataCorrupted, message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static InvalidCastException InvalidCast<TFrom, TTo>(string name, Exception? innerException = default)
|
||||
{
|
||||
|
||||
@@ -9,8 +9,6 @@ internal enum HutaoExceptionKind
|
||||
|
||||
// Foundation
|
||||
ImageCacheInvalidUri,
|
||||
DatabaseCorrupted,
|
||||
UserdataCorrupted,
|
||||
|
||||
// IO
|
||||
FileSystemCreateFileInsufficientPermissions,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
|
||||
@@ -78,36 +77,34 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
using (HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent())
|
||||
|
||||
Memory<byte> buffer = new byte[bufferSize];
|
||||
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
Memory<byte> buffer = memoryOwner.Memory;
|
||||
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||
int totalBytesRead = 0;
|
||||
int bytesReadAfterPreviousReport = 0;
|
||||
do
|
||||
{
|
||||
int totalBytesRead = 0;
|
||||
int bytesReadAfterPreviousReport = 0;
|
||||
do
|
||||
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
bytesReadAfterPreviousReport += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
bytesReadAfterPreviousReport += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
@@ -52,30 +51,26 @@ internal class StreamCopyWorker<TStatus>
|
||||
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
Memory<byte> buffer = new byte[bufferSize];
|
||||
|
||||
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(bufferSize))
|
||||
do
|
||||
{
|
||||
Memory<byte> buffer = memoryOwner.Memory;
|
||||
|
||||
do
|
||||
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
|
||||
if (bytesRead is 0)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
break;
|
||||
}
|
||||
|
||||
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
break;
|
||||
}
|
||||
|
||||
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
while (bytesRead > 0);
|
||||
}
|
||||
while (bytesRead > 0);
|
||||
}
|
||||
}
|
||||
@@ -116,15 +116,15 @@ internal sealed partial class Activation : IActivation
|
||||
|
||||
// If it's the first time launch, we show the guide window anyway.
|
||||
// Otherwise, we check if there's any unfulfilled resource category present.
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin)
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin)
|
||||
{
|
||||
if (StaticResource.IsAnyUnfulfilledCategoryPresent())
|
||||
{
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
}
|
||||
}
|
||||
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) < GuideState.Completed)
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language) < GuideState.Completed)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
serviceProvider.GetRequiredService<GuideWindow>();
|
||||
|
||||
@@ -20,9 +20,7 @@ internal static class SettingKeys
|
||||
#region Application
|
||||
public const string LaunchTimes = "LaunchTimes";
|
||||
public const string DataFolderPath = "DataFolderPath";
|
||||
public const string Major1Minor10Revision0GuideState = "Major1Minor10Revision0GuideState1";
|
||||
public const string StaticResourceImageQuality = "StaticResourceImageQuality";
|
||||
public const string StaticResourceImageArchive = "StaticResourceImageArchive";
|
||||
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
||||
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
|
||||
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
|
||||
#endregion
|
||||
@@ -62,10 +60,6 @@ internal static class SettingKeys
|
||||
#endregion
|
||||
|
||||
#region Obsolete
|
||||
|
||||
[Obsolete("重置新手引导状态")]
|
||||
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
||||
|
||||
[Obsolete("重置调试控制台开关")]
|
||||
public const string IsAllocConsoleDebugModeEnabledLegacy1 = "IsAllocConsoleDebugModeEnabled";
|
||||
#endregion
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:shvg="using:Snap.Hutao.View.Guide"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid" Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
|
||||
@@ -14,10 +14,10 @@ namespace Snap.Hutao;
|
||||
internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
|
||||
{
|
||||
private const int MinWidth = 1000;
|
||||
private const int MinHeight = 650;
|
||||
private const int MinHeight = 600;
|
||||
|
||||
private const int MaxWidth = 1200;
|
||||
private const int MaxHeight = 800;
|
||||
private const int MaxHeight = 750;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Entity.Abstraction;
|
||||
|
||||
internal interface IAppDbEntity
|
||||
{
|
||||
Guid InnerId { get; set; }
|
||||
}
|
||||
@@ -16,8 +16,8 @@ namespace Snap.Hutao.Model.Entity;
|
||||
[HighQuality]
|
||||
[Table("achievements")]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal sealed class Achievement : IAppDbEntity,
|
||||
IEquatable<Achievement>,
|
||||
internal sealed class Achievement
|
||||
: IEquatable<Achievement>,
|
||||
IDbMappingForeignKeyFrom<Achievement, AchievementId>,
|
||||
IDbMappingForeignKeyFrom<Achievement, UIAFItem>
|
||||
{
|
||||
|
||||
@@ -19,7 +19,6 @@ internal sealed partial class SettingEntry
|
||||
public const string AnnouncementRegion = "AnnouncementRegion";
|
||||
|
||||
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
|
||||
public const string IsNeverHeldStatisticsItemVisible = "IsNeverHeldStatisticsItemVisible";
|
||||
|
||||
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
|
||||
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -653,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>Open UIAF Json File</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<value>Multiple identical achievement IDs found in a single achievement archive</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -653,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>Buka berkas UIAF Json</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<value>Terdapat beberapa ID pencapaian yang identik dalam satu arsip pencapaian</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -653,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>UIAF Json ファイルを開く</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<value>複数の同一アチーブメント Idがアーカイブに混在しています</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -653,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>打开 UIAF Json 文件</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<value>한 업적 아카이브에서 Id 동일한 업적 발견됨</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -653,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>Abrir arquivo Json UIAF</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<value>Várias IDs de conquistas idênticas encontradas em um único arquivo de conquistas</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
|
||||
@@ -653,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>打开 UIAF Json 文件</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<value>单个成就存档内发现多个相同的成就 Id</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
@@ -1382,9 +1382,6 @@
|
||||
<data name="ViewGachaLogHeader" xml:space="preserve">
|
||||
<value>祈愿记录</value>
|
||||
</data>
|
||||
<data name="ViewGuideStaticResourceDownloadSize" xml:space="preserve">
|
||||
<value>预计下载大小:{0}</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepAgreementIHaveReadText" xml:space="preserve">
|
||||
<value>我已阅读并同意</value>
|
||||
</data>
|
||||
@@ -1400,12 +1397,6 @@
|
||||
<data name="ViewGuideStepAgreementTermOfService" xml:space="preserve">
|
||||
<value>用户使用协议与法律声明</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepCommonSetting" xml:space="preserve">
|
||||
<value>基础设置</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepCommonSettingHint" xml:space="preserve">
|
||||
<value>稍后可以在设置中修改</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepDocument" xml:space="preserve">
|
||||
<value>文档</value>
|
||||
</data>
|
||||
@@ -1416,7 +1407,7 @@
|
||||
<value>安装完成后重启胡桃以查看是否正常生效</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepEnvironmentFontDescription1" xml:space="preserve">
|
||||
<value>如果上方的图标中存在乱码或方块字,请前往</value>
|
||||
<value>如果上方的图标中存在乱码,请前往</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepEnvironmentFontDescription2" xml:space="preserve">
|
||||
<value>下载并自行安装图标字体</value>
|
||||
@@ -1433,24 +1424,6 @@
|
||||
<data name="ViewGuideStepStaticResource" xml:space="preserve">
|
||||
<value>资源</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSetting" xml:space="preserve">
|
||||
<value>图像资源设置</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingHint" xml:space="preserve">
|
||||
<value>* 除非你卸载并重新安装胡桃,否则你将无法更改这些设置</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingMinimumHeader" xml:space="preserve">
|
||||
<value>图片资源包体</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingMinimumOff" xml:space="preserve">
|
||||
<value>完整包体</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingMinimumOn" xml:space="preserve">
|
||||
<value>精简包体</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingQualityHeader" xml:space="preserve">
|
||||
<value>图片资源质量</value>
|
||||
</data>
|
||||
<data name="ViewHutaoDatabaseHeader" xml:space="preserve">
|
||||
<value>深渊统计</value>
|
||||
</data>
|
||||
@@ -1640,12 +1613,6 @@
|
||||
<data name="ViewModelGuideActionStaticResourceBegin" xml:space="preserve">
|
||||
<value>下载资源文件中,请稍候</value>
|
||||
</data>
|
||||
<data name="ViewModelGuideStaticResourceQualityHigh" xml:space="preserve">
|
||||
<value>高质量</value>
|
||||
</data>
|
||||
<data name="ViewModelGuideStaticResourceQualityRaw" xml:space="preserve">
|
||||
<value>原图</value>
|
||||
</data>
|
||||
<data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve">
|
||||
<value>请输入正确的邮箱</value>
|
||||
</data>
|
||||
@@ -2636,12 +2603,6 @@
|
||||
<data name="ViewPageSettingKeyShortcutHeader" xml:space="preserve">
|
||||
<value>快捷键</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingNeverHeldItemVisibleDescription" xml:space="preserve">
|
||||
<value>在祈愿记录页面显示或隐藏未持有过的角色或武器</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingNeverHeldItemVisibleHeader" xml:space="preserve">
|
||||
<value>未持有过的角色或武器</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingOfficialSiteNavigate" xml:space="preserve">
|
||||
<value>前往官网</value>
|
||||
</data>
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -653,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>Открыть UIAF Json файл</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<value>В одном архиве достижений обнаружено несколько одинаковых идентификаторов достижений</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -653,7 +653,7 @@
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>打開 UIAF Json 文件</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedAchievementIdNotUnique" xml:space="preserve">
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<value>單個成就存檔內發現多個相同的成就 Id</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoPropertyAtk" xml:space="preserve">
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal static class AppDbServiceAppDbEntityExtension
|
||||
{
|
||||
public static int DeleteByInnerId<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class, IAppDbEntity
|
||||
{
|
||||
return service.Execute(dbset => dbset.ExecuteDeleteWhere(e => e.InnerId == entity.InnerId));
|
||||
}
|
||||
|
||||
public static ValueTask<int> DeleteByInnerIdAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class, IAppDbEntity
|
||||
{
|
||||
return service.ExecuteAsync((dbset, token) => dbset.ExecuteDeleteWhereAsync(e => e.InnerId == entity.InnerId, token), token);
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal static class AppDbServiceExtension
|
||||
{
|
||||
public static TResult Execute<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, TResult> func)
|
||||
where TEntity : class
|
||||
{
|
||||
using (IServiceScope scope = service.ServiceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
return func(appDbContext.Set<TEntity>());
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, ValueTask<TResult>> asyncFunc)
|
||||
where TEntity : class
|
||||
{
|
||||
using (IServiceScope scope = service.ServiceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
return await asyncFunc(appDbContext.Set<TEntity>()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, CancellationToken, ValueTask<TResult>> asyncFunc, CancellationToken token)
|
||||
where TEntity : class
|
||||
{
|
||||
using (IServiceScope scope = service.ServiceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
return await asyncFunc(appDbContext.Set<TEntity>(), token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, Task<TResult>> asyncFunc)
|
||||
where TEntity : class
|
||||
{
|
||||
using (IServiceScope scope = service.ServiceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
return await asyncFunc(appDbContext.Set<TEntity>()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, CancellationToken, Task<TResult>> asyncFunc, CancellationToken token)
|
||||
where TEntity : class
|
||||
{
|
||||
using (IServiceScope scope = service.ServiceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
return await asyncFunc(appDbContext.Set<TEntity>(), token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static int Add<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.AddAndSave(entity));
|
||||
}
|
||||
|
||||
public static ValueTask<int> AddAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync((dbset, token) => dbset.AddAndSaveAsync(entity, token), token);
|
||||
}
|
||||
|
||||
public static int AddRange<TEntity>(this IAppDbService<TEntity> service, IEnumerable<TEntity> entities)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.AddRangeAndSave(entities));
|
||||
}
|
||||
|
||||
public static ValueTask<int> AddRangeAsync<TEntity>(this IAppDbService<TEntity> service, IEnumerable<TEntity> entities, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync((dbset, token) => dbset.AddRangeAndSaveAsync(entities, token), token);
|
||||
}
|
||||
|
||||
public static TEntity Single<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.AsNoTracking().Single(predicate));
|
||||
}
|
||||
|
||||
public static ValueTask<TEntity> SingleAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync((dbset, token) => dbset.AsNoTracking().SingleAsync(predicate, token), token);
|
||||
}
|
||||
|
||||
public static TResult Query<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, TResult> func)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => func(dbset.AsNoTracking()));
|
||||
}
|
||||
|
||||
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, ValueTask<TResult>> func)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync(dbset => func(dbset.AsNoTracking()));
|
||||
}
|
||||
|
||||
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, CancellationToken, ValueTask<TResult>> func, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync((dbset, token) => func(dbset.AsNoTracking(), token), token);
|
||||
}
|
||||
|
||||
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, Task<TResult>> func)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync(dbset => func(dbset.AsNoTracking()));
|
||||
}
|
||||
|
||||
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, CancellationToken, Task<TResult>> func, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync((dbset, token) => func(dbset.AsNoTracking(), token), token);
|
||||
}
|
||||
|
||||
public static int Update<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.UpdateAndSave(entity));
|
||||
}
|
||||
|
||||
public static ValueTask<int> UpdateAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync((dbset, token) => dbset.UpdateAndSaveAsync(entity, token), token);
|
||||
}
|
||||
|
||||
public static int Delete<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.RemoveAndSave(entity));
|
||||
}
|
||||
|
||||
public static ValueTask<int> DeleteAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync((dbset, token) => dbset.RemoveAndSaveAsync(entity, token), token);
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,20 @@ namespace Snap.Hutao.Service.Abstraction;
|
||||
/// 数据库存储选项的设置
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
internal abstract partial class DbStoreOptions : ObservableObject
|
||||
internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbStoreOptions>
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DbStoreOptions Value { get => this; }
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中获取字符串数据
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>值</returns>
|
||||
protected string GetOption(ref string? storage, string key, string defaultValue = "")
|
||||
{
|
||||
return GetOption(ref storage, key, () => defaultValue);
|
||||
@@ -39,6 +49,13 @@ internal abstract partial class DbStoreOptions : ObservableObject
|
||||
return storage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中获取bool数据
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>值</returns>
|
||||
protected bool GetOption(ref bool? storage, string key, bool defaultValue = false)
|
||||
{
|
||||
return GetOption(ref storage, key, () => defaultValue);
|
||||
@@ -61,6 +78,13 @@ internal abstract partial class DbStoreOptions : ObservableObject
|
||||
return storage.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中获取int数据
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>值</returns>
|
||||
protected int GetOption(ref int? storage, string key, int defaultValue = 0)
|
||||
{
|
||||
return GetOption(ref storage, key, () => defaultValue);
|
||||
@@ -83,6 +107,15 @@ internal abstract partial class DbStoreOptions : ObservableObject
|
||||
return storage.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中获取任何类型的数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据的类型</typeparam>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="deserializer">反序列化器</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>值</returns>
|
||||
[return: NotNull]
|
||||
protected T GetOption<T>(ref T? storage, string key, Func<string, T> deserializer, [DisallowNull] T defaultValue)
|
||||
{
|
||||
@@ -127,6 +160,13 @@ internal abstract partial class DbStoreOptions : ObservableObject
|
||||
return storage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将值存入数据库
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
protected void SetOption(ref string? storage, string key, string? value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (!SetProperty(ref storage, value, propertyName))
|
||||
@@ -142,6 +182,14 @@ internal abstract partial class DbStoreOptions : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将值存入数据库
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
/// <returns>是否设置了值</returns>
|
||||
protected bool SetOption(ref bool? storage, string key, bool value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
bool set = SetProperty(ref storage, value, propertyName);
|
||||
@@ -160,6 +208,13 @@ internal abstract partial class DbStoreOptions : ObservableObject
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将值存入数据库
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
protected void SetOption(ref int? storage, string key, int value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (!SetProperty(ref storage, value, propertyName))
|
||||
@@ -175,6 +230,15 @@ internal abstract partial class DbStoreOptions : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将值存入数据库
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据的类型</typeparam>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="serializer">序列化器</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
protected void SetOption<T>(ref T? storage, string key, T value, Func<T, string> serializer, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (!SetProperty(ref storage, value, propertyName))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||
|
||||
namespace Snap.Hutao.Service.Announcement;
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 公告服务
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal interface IAppDbService<TEntity> : IAppInfrastructureService
|
||||
where TEntity : class
|
||||
{
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal interface IAppInfrastructureService
|
||||
{
|
||||
IServiceProvider ServiceProvider { get; }
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal interface IAppService;
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal static class ServiceScopeExtension
|
||||
{
|
||||
public static TService GetRequiredService<TService>(this IServiceScope scope)
|
||||
where TService : class
|
||||
{
|
||||
return scope.ServiceProvider.GetRequiredService<TService>();
|
||||
}
|
||||
|
||||
public static TDbContext GetDbContext<TDbContext>(this IServiceScope scope)
|
||||
where TDbContext : DbContext
|
||||
{
|
||||
return scope.GetRequiredService<TDbContext>();
|
||||
}
|
||||
|
||||
public static AppDbContext GetAppDbContext(this IServiceScope scope)
|
||||
{
|
||||
return scope.GetDbContext<AppDbContext>();
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,9 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Collection;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
|
||||
|
||||
namespace Snap.Hutao.Service.Achievement;
|
||||
@@ -23,155 +21,197 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ILogger<AchievementDbBulkOperation> logger;
|
||||
|
||||
/// <summary>
|
||||
/// 合并
|
||||
/// </summary>
|
||||
/// <param name="archiveId">成就id</param>
|
||||
/// <param name="items">待合并的项</param>
|
||||
/// <param name="aggressive">是否贪婪</param>
|
||||
/// <returns>导入结果</returns>
|
||||
public ImportResult Merge(Guid archiveId, IEnumerable<UIAFItem> items, bool aggressive)
|
||||
{
|
||||
logger.LogInformation("Perform merge operation for [Archive: {Id}], [Aggressive: {Aggressive}]", archiveId, aggressive);
|
||||
logger.LogInformation("Perform {Method} Operation for archive: {Id}, Aggressive: {Aggressive}", nameof(Merge), archiveId, aggressive);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
IOrderedQueryable<EntityAchievement> oldData = appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.OrderBy(a => a.Id);
|
||||
|
||||
(int add, int update) = (0, 0);
|
||||
int add = 0;
|
||||
int update = 0;
|
||||
|
||||
using (TwoEnumerbleEnumerator<EntityAchievement, UIAFItem> enumerator = new(oldData, items))
|
||||
using (IEnumerator<EntityAchievement> entityEnumerator = oldData.GetEnumerator())
|
||||
{
|
||||
(bool moveEntity, bool moveUIAF) = (true, true);
|
||||
|
||||
while (true)
|
||||
using (IEnumerator<UIAFItem> uiafEnumerator = items.GetEnumerator())
|
||||
{
|
||||
if (!enumerator.MoveNext(ref moveEntity, ref moveUIAF))
|
||||
{
|
||||
break;
|
||||
}
|
||||
bool moveEntity = true;
|
||||
bool moveUIAF = true;
|
||||
|
||||
(EntityAchievement? entity, UIAFItem? uiaf) = enumerator.Current;
|
||||
|
||||
switch (entity, uiaf)
|
||||
while (true)
|
||||
{
|
||||
case (null, not null):
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
add++;
|
||||
continue;
|
||||
case (not null, null):
|
||||
continue; // Skipped
|
||||
default:
|
||||
bool moveEntityResult = moveEntity && entityEnumerator.MoveNext();
|
||||
bool moveUIAFResult = moveUIAF && uiafEnumerator.MoveNext();
|
||||
|
||||
if (!(moveEntityResult || moveUIAFResult))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
EntityAchievement? entity = entityEnumerator.Current;
|
||||
UIAFItem? uiaf = uiafEnumerator.Current;
|
||||
|
||||
if (entity is null && uiaf is not null)
|
||||
{
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
add++;
|
||||
continue;
|
||||
}
|
||||
else if (entity is not null && uiaf is null)
|
||||
{
|
||||
// skip
|
||||
continue;
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
ArgumentNullException.ThrowIfNull(uiaf);
|
||||
|
||||
switch (entity.Id.CompareTo(uiaf.Id))
|
||||
if (entity.Id < uiaf.Id)
|
||||
{
|
||||
case < 0:
|
||||
(moveEntity, moveUIAF) = (true, false);
|
||||
break;
|
||||
case 0:
|
||||
(moveEntity, moveUIAF) = (true, true);
|
||||
|
||||
if (aggressive)
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(entity);
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
update++;
|
||||
}
|
||||
|
||||
break;
|
||||
case > 0:
|
||||
(moveEntity, moveUIAF) = (false, true);
|
||||
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
add++;
|
||||
break;
|
||||
moveEntity = true;
|
||||
moveUIAF = false;
|
||||
}
|
||||
else if (entity.Id == uiaf.Id)
|
||||
{
|
||||
moveEntity = true;
|
||||
moveUIAF = true;
|
||||
|
||||
break;
|
||||
if (aggressive)
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(entity);
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
update++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// entity.Id > uiaf.Id
|
||||
moveEntity = false;
|
||||
moveUIAF = true;
|
||||
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
add++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Merge operation complete, [Add: {Add}], [Update: {Update}]", add, update);
|
||||
logger.LogInformation("{Method} Operation Complete, Add: {Add}, Update: {Update}", nameof(Merge), add, update);
|
||||
return new(add, update, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 覆盖
|
||||
/// </summary>
|
||||
/// <param name="archiveId">成就id</param>
|
||||
/// <param name="items">待覆盖的项</param>
|
||||
/// <returns>导入结果</returns>
|
||||
public ImportResult Overwrite(Guid archiveId, IEnumerable<EntityAchievement> items)
|
||||
{
|
||||
logger.LogInformation("Perform Overwrite Operation for [Archive: {Id}]", archiveId);
|
||||
logger.LogInformation("Perform {Method} Operation for archive: {Id}", nameof(Overwrite), archiveId);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
IOrderedQueryable<EntityAchievement> oldData = appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.OrderBy(a => a.Id);
|
||||
|
||||
(int add, int update, int remove) = (0, 0, 0);
|
||||
int add = 0;
|
||||
int update = 0;
|
||||
int remove = 0;
|
||||
|
||||
using (TwoEnumerbleEnumerator<EntityAchievement, EntityAchievement> enumerator = new(oldData, items))
|
||||
using (IEnumerator<EntityAchievement> oldDataEnumerator = oldData.GetEnumerator())
|
||||
{
|
||||
(bool moveOld, bool moveNew) = (true, true);
|
||||
|
||||
while (true)
|
||||
using (IEnumerator<EntityAchievement> newDataEnumerator = items.GetEnumerator())
|
||||
{
|
||||
if (!enumerator.MoveNext(ref moveOld, ref moveNew))
|
||||
{
|
||||
break;
|
||||
}
|
||||
bool moveOld = true;
|
||||
bool moveNew = true;
|
||||
|
||||
(EntityAchievement? oldEntity, EntityAchievement? newEntity) = enumerator.Current;
|
||||
|
||||
switch (oldEntity, newEntity)
|
||||
while (true)
|
||||
{
|
||||
case (null, not null):
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
add++;
|
||||
continue;
|
||||
case (not null, null):
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
remove++;
|
||||
continue;
|
||||
default:
|
||||
bool moveOldResult = moveOld && oldDataEnumerator.MoveNext();
|
||||
bool moveNewResult = moveNew && newDataEnumerator.MoveNext();
|
||||
|
||||
if (moveOldResult || moveNewResult)
|
||||
{
|
||||
EntityAchievement? oldEntity = oldDataEnumerator.Current;
|
||||
EntityAchievement? newEntity = newDataEnumerator.Current;
|
||||
|
||||
if (oldEntity is null && newEntity is not null)
|
||||
{
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
add++;
|
||||
continue;
|
||||
}
|
||||
else if (oldEntity is not null && newEntity is null)
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
remove++;
|
||||
continue;
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(oldEntity);
|
||||
ArgumentNullException.ThrowIfNull(newEntity);
|
||||
|
||||
switch (oldEntity.Id.CompareTo(newEntity.Id))
|
||||
if (oldEntity.Id < newEntity.Id)
|
||||
{
|
||||
case < 0:
|
||||
(moveOld, moveNew) = (true, false);
|
||||
break;
|
||||
case 0:
|
||||
(moveOld, moveNew) = (true, true);
|
||||
|
||||
if (oldEntity.Equals(newEntity))
|
||||
{
|
||||
// Skip same entry, reduce write operation.
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
update++;
|
||||
}
|
||||
|
||||
break;
|
||||
case > 0:
|
||||
(moveOld, moveNew) = (false, true);
|
||||
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
add++;
|
||||
break;
|
||||
moveOld = true;
|
||||
moveNew = false;
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
remove++;
|
||||
}
|
||||
else if (oldEntity.Id == newEntity.Id)
|
||||
{
|
||||
moveOld = true;
|
||||
moveNew = true;
|
||||
|
||||
if (oldEntity.Equals(newEntity))
|
||||
{
|
||||
// skip same entry.
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
update++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// entity.Id > uiaf.Id
|
||||
moveOld = false;
|
||||
moveNew = true;
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
add++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Overwrite Operation Complete, Add: {Add}, Update: {Update}, Remove: {Remove}", add, update, remove);
|
||||
logger.LogInformation("{Method} Operation Complete, Add: {Add}, Update: {Update}, Remove: {Remove}", nameof(Overwrite), add, update, remove);
|
||||
return new(add, update, remove);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using System.Collections.ObjectModel;
|
||||
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
|
||||
|
||||
@@ -20,95 +21,148 @@ internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public IServiceProvider ServiceProvider { get => serviceProvider; }
|
||||
|
||||
public Dictionary<AchievementId, EntityAchievement> GetAchievementMapByArchiveId(Guid archiveId)
|
||||
{
|
||||
Dictionary<AchievementId, EntityAchievement> entities;
|
||||
try
|
||||
{
|
||||
return this.Query<EntityAchievement, Dictionary<AchievementId, EntityAchievement>>(query => query
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.ToDictionary(a => (AchievementId)a.Id));
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
entities = appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.ToDictionary(a => (AchievementId)a.Id);
|
||||
}
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
throw HutaoException.UserdataCorrupted(SH.ServiceAchievementUserdataCorruptedAchievementIdNotUnique, ex);
|
||||
throw ThrowHelper.DatabaseCorrupted(SH.ServiceAchievementUserdataCorruptedInnerIdNotUnique, ex);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public async ValueTask<int> GetFinishedAchievementCountByArchiveIdAsync(Guid archiveId)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return await appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
.CountAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<int> GetFinishedAchievementCountByArchiveIdAsync(Guid archiveId, CancellationToken token = default)
|
||||
{
|
||||
return this.QueryAsync<EntityAchievement, int>(
|
||||
(query, token) => query
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
.CountAsync(token),
|
||||
token);
|
||||
}
|
||||
|
||||
[SuppressMessage("", "CA1305")]
|
||||
public ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take, CancellationToken token = default)
|
||||
public async ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take)
|
||||
{
|
||||
return this.QueryAsync<EntityAchievement, List<EntityAchievement>>(
|
||||
(query, token) => query
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return await appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
.OrderByDescending(a => a.Time.ToString())
|
||||
.Take(take)
|
||||
.ToListAsync(token),
|
||||
token);
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void OverwriteAchievement(EntityAchievement achievement)
|
||||
{
|
||||
this.DeleteByInnerId(achievement);
|
||||
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
this.Add(achievement);
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// Delete exists one.
|
||||
appDbContext.Achievements.ExecuteDeleteWhere(e => e.InnerId == achievement.InnerId);
|
||||
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
{
|
||||
appDbContext.Achievements.AddAndSave(achievement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask OverwriteAchievementAsync(EntityAchievement achievement, CancellationToken token = default)
|
||||
public async ValueTask OverwriteAchievementAsync(EntityAchievement achievement)
|
||||
{
|
||||
await this.DeleteByInnerIdAsync(achievement, token).ConfigureAwait(false);
|
||||
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
await this.AddAsync(achievement, token).ConfigureAwait(false);
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// Delete exists one.
|
||||
await appDbContext.Achievements.ExecuteDeleteWhereAsync(e => e.InnerId == achievement.InnerId).ConfigureAwait(false);
|
||||
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
{
|
||||
await appDbContext.Achievements.AddAndSaveAsync(achievement).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<AchievementArchive> GetAchievementArchiveCollection()
|
||||
{
|
||||
return this.Query<AchievementArchive, ObservableCollection<AchievementArchive>>(query => query.ToObservableCollection());
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.AchievementArchives.AsNoTracking().ToObservableCollection();
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask RemoveAchievementArchiveAsync(AchievementArchive archive, CancellationToken token = default)
|
||||
public async ValueTask RemoveAchievementArchiveAsync(AchievementArchive archive)
|
||||
{
|
||||
// It will cascade deleted the achievements.
|
||||
await this.DeleteAsync(archive, token).ConfigureAwait(false);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// It will cascade deleted the achievements.
|
||||
await appDbContext.AchievementArchives.RemoveAndSaveAsync(archive).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public List<EntityAchievement> GetAchievementListByArchiveId(Guid archiveId)
|
||||
{
|
||||
return this.Query<EntityAchievement, List<EntityAchievement>>(query => [.. query.Where(a => a.ArchiveId == archiveId)]);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
IQueryable<EntityAchievement> result = appDbContext.Achievements.AsNoTracking().Where(i => i.ArchiveId == archiveId);
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId, CancellationToken token = default)
|
||||
public async ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId)
|
||||
{
|
||||
return this.QueryAsync<EntityAchievement, List<EntityAchievement>>(
|
||||
(query, token) => query
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.ToListAsync(token),
|
||||
token);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return await appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(i => i.ArchiveId == archiveId)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public List<AchievementArchive> GetAchievementArchiveList()
|
||||
{
|
||||
return this.Query<AchievementArchive, List<AchievementArchive>>(query => [.. query]);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
IQueryable<AchievementArchive> result = appDbContext.AchievementArchives.AsNoTracking();
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<List<AchievementArchive>> GetAchievementArchiveListAsync(CancellationToken token = default)
|
||||
public async ValueTask<List<AchievementArchive>> GetAchievementArchiveListAsync()
|
||||
{
|
||||
return await this.QueryAsync<AchievementArchive, List<AchievementArchive>>(query => query.ToListAsync()).ConfigureAwait(false);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return await appDbContext.AchievementArchives.AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,33 +2,32 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using System.Collections.ObjectModel;
|
||||
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
|
||||
|
||||
namespace Snap.Hutao.Service.Achievement;
|
||||
|
||||
internal interface IAchievementDbService : IAppDbService<Model.Entity.AchievementArchive>, IAppDbService<EntityAchievement>
|
||||
internal interface IAchievementDbService
|
||||
{
|
||||
ValueTask RemoveAchievementArchiveAsync(Model.Entity.AchievementArchive archive, CancellationToken token = default);
|
||||
ValueTask RemoveAchievementArchiveAsync(Model.Entity.AchievementArchive archive);
|
||||
|
||||
ObservableCollection<Model.Entity.AchievementArchive> GetAchievementArchiveCollection();
|
||||
|
||||
List<Model.Entity.AchievementArchive> GetAchievementArchiveList();
|
||||
|
||||
ValueTask<List<Model.Entity.AchievementArchive>> GetAchievementArchiveListAsync(CancellationToken token = default);
|
||||
ValueTask<List<Model.Entity.AchievementArchive>> GetAchievementArchiveListAsync();
|
||||
|
||||
List<EntityAchievement> GetAchievementListByArchiveId(Guid archiveId);
|
||||
|
||||
ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId, CancellationToken token = default);
|
||||
ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId);
|
||||
|
||||
Dictionary<AchievementId, EntityAchievement> GetAchievementMapByArchiveId(Guid archiveId);
|
||||
|
||||
ValueTask<int> GetFinishedAchievementCountByArchiveIdAsync(Guid archiveId, CancellationToken token = default);
|
||||
ValueTask<int> GetFinishedAchievementCountByArchiveIdAsync(Guid archiveId);
|
||||
|
||||
ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take, CancellationToken token = default);
|
||||
ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take);
|
||||
|
||||
void OverwriteAchievement(EntityAchievement achievement);
|
||||
|
||||
ValueTask OverwriteAchievementAsync(EntityAchievement achievement, CancellationToken token = default);
|
||||
ValueTask OverwriteAchievementAsync(EntityAchievement achievement);
|
||||
}
|
||||
@@ -3,14 +3,13 @@
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Service.Announcement;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using WebAnnouncement = Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement.Announcement;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
|
||||
@@ -73,7 +72,7 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
// 将公告内容联入公告列表
|
||||
foreach (ref readonly AnnouncementListWrapper listWrapper in CollectionsMarshal.AsSpan(announcementListWrappers))
|
||||
{
|
||||
foreach (ref readonly WebAnnouncement item in CollectionsMarshal.AsSpan(listWrapper.List))
|
||||
foreach (ref readonly Announcement item in CollectionsMarshal.AsSpan(listWrapper.List))
|
||||
{
|
||||
contentMap.TryGetValue(item.AnnId, out string? rawContent);
|
||||
item.Content = rawContent ?? string.Empty;
|
||||
@@ -84,7 +83,7 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
|
||||
foreach (ref readonly AnnouncementListWrapper listWrapper in CollectionsMarshal.AsSpan(announcementListWrappers))
|
||||
{
|
||||
foreach (ref readonly WebAnnouncement item in CollectionsMarshal.AsSpan(listWrapper.List))
|
||||
foreach (ref readonly Announcement item in CollectionsMarshal.AsSpan(listWrapper.List))
|
||||
{
|
||||
item.Subtitle = new StringBuilder(item.Subtitle)
|
||||
.Replace("\r<br>", string.Empty)
|
||||
@@ -100,12 +99,12 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
private static void AdjustAnnouncementTime(List<AnnouncementListWrapper> announcementListWrappers, in TimeSpan offset)
|
||||
{
|
||||
// 活动公告
|
||||
List<WebAnnouncement> activities = announcementListWrappers
|
||||
List<Announcement> activities = announcementListWrappers
|
||||
.Single(wrapper => wrapper.TypeId == 1)
|
||||
.List;
|
||||
|
||||
// 更新公告
|
||||
WebAnnouncement versionUpdate = announcementListWrappers
|
||||
Announcement versionUpdate = announcementListWrappers
|
||||
.Single(wrapper => wrapper.TypeId == 2)
|
||||
.List
|
||||
.Single(ann => AnnouncementRegex.VersionUpdateTitleRegex.IsMatch(ann.Title));
|
||||
@@ -117,7 +116,7 @@ internal sealed partial class AnnouncementService : IAnnouncementService
|
||||
|
||||
DateTimeOffset versionUpdateTime = UnsafeDateTimeOffset.ParseDateTime(versionMatch.Groups[1].ValueSpan, offset);
|
||||
|
||||
foreach (ref readonly WebAnnouncement announcement in CollectionsMarshal.AsSpan(activities))
|
||||
foreach (ref readonly Announcement announcement in CollectionsMarshal.AsSpan(activities))
|
||||
{
|
||||
if (AnnouncementRegex.PermanentActivityAfterUpdateTimeRegex.Match(announcement.Content) is { Success: true } permanent)
|
||||
{
|
||||
@@ -15,7 +15,6 @@ namespace Snap.Hutao.Service;
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class AppOptions : DbStoreOptions
|
||||
{
|
||||
private bool? isNeverHeldStatisticsItemVisible;
|
||||
private bool? isEmptyHistoryWishVisible;
|
||||
private BackdropType? backdropType;
|
||||
private ElementTheme? elementTheme;
|
||||
@@ -29,12 +28,6 @@ internal sealed partial class AppOptions : DbStoreOptions
|
||||
set => SetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible, value);
|
||||
}
|
||||
|
||||
public bool IsNeverHeldStatisticsItemVisible
|
||||
{
|
||||
get => GetOption(ref isNeverHeldStatisticsItemVisible, SettingEntry.IsNeverHeldStatisticsItemVisible);
|
||||
set => SetOption(ref isNeverHeldStatisticsItemVisible, SettingEntry.IsNeverHeldStatisticsItemVisible, value);
|
||||
}
|
||||
|
||||
public List<NameValue<BackdropType>> BackdropTypes { get; } = CollectionsNameValue.FromEnum<BackdropType>(type => type >= 0);
|
||||
|
||||
public BackdropType BackdropType
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using Snap.Discord.GameSDK.ABI;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Unicode;
|
||||
|
||||
namespace Snap.Hutao.Service.Discord;
|
||||
@@ -159,7 +158,7 @@ internal static class DiscordController
|
||||
static unsafe void DebugWriteDiscordMessage(void* state, DiscordLogLevel logLevel, sbyte* ptr)
|
||||
{
|
||||
ReadOnlySpan<byte> utf8 = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)ptr);
|
||||
string message = Encoding.UTF8.GetString(utf8);
|
||||
string message = System.Text.Encoding.UTF8.GetString(utf8);
|
||||
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK]:[{logLevel}]:{message}");
|
||||
}
|
||||
}
|
||||
@@ -225,18 +224,18 @@ internal static class DiscordController
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void SetString(sbyte* reference, int length, in ReadOnlySpan<char> source)
|
||||
private static unsafe void SetString(sbyte* reference, int length, string source)
|
||||
{
|
||||
Span<byte> bytes = new(reference, length);
|
||||
bytes.Clear();
|
||||
Utf8.FromUtf16(source, bytes, out _, out _);
|
||||
Span<sbyte> sbytes = new(reference, length);
|
||||
sbytes.Clear();
|
||||
Utf8.FromUtf16(source.AsSpan(), MemoryMarshal.Cast<sbyte, byte>(sbytes), out _, out _);
|
||||
}
|
||||
|
||||
private static unsafe void SetString(sbyte* reference, int length, in ReadOnlySpan<byte> source)
|
||||
{
|
||||
Span<byte> bytes = new(reference, length);
|
||||
bytes.Clear();
|
||||
source.CopyTo(bytes);
|
||||
Span<sbyte> sbytes = new(reference, length);
|
||||
sbytes.Clear();
|
||||
source.CopyTo(MemoryMarshal.Cast<sbyte, byte>(sbytes));
|
||||
}
|
||||
|
||||
private struct DiscordAsyncAction
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
@@ -11,7 +10,6 @@ using Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
using Snap.Hutao.ViewModel.GachaLog;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
using Snap.Hutao.Web.Hutao.GachaLog;
|
||||
using System.Collections.Frozen;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog.Factory;
|
||||
@@ -24,16 +22,6 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
|
||||
[Injection(InjectAs.Scoped, typeof(IGachaStatisticsFactory))]
|
||||
internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
{
|
||||
private static readonly FrozenSet<uint> BlueStandardWeaponIdsSet = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
11301U, 11302U, 11306U, 12301U, 12302U, 12305U, 13303U, 14301U, 14302U, 14304U, 15301U, 15302U, 15304U
|
||||
]);
|
||||
|
||||
private static readonly FrozenSet<uint> PurpleStandardWeaponIdsSet = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
11401U, 11402U, 11403U, 11405U, 12401U, 12402U, 12403U, 12405U, 13401U, 13407U, 14401U, 14402U, 14403U, 14409U, 15401U, 15402U, 15403U, 15405U
|
||||
]);
|
||||
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly HomaGachaLogClient homaGachaLogClient;
|
||||
private readonly ITaskContext taskContext;
|
||||
@@ -45,7 +33,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
List<HistoryWishBuilder> historyWishBuilders = context.GachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
|
||||
return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible, options.IsNeverHeldStatisticsItemVisible);
|
||||
return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);
|
||||
}
|
||||
|
||||
private static GachaStatistics CreateCore(
|
||||
@@ -54,8 +42,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
List<Model.Entity.GachaItem> items,
|
||||
List<HistoryWishBuilder> historyWishBuilders,
|
||||
in GachaLogServiceMetadataContext context,
|
||||
bool isEmptyHistoryWishVisible,
|
||||
bool isNeverHeldStatisticsItemVisible)
|
||||
bool isEmptyHistoryWishVisible)
|
||||
{
|
||||
TypedWishSummaryBuilderContext standardContext = TypedWishSummaryBuilderContext.StandardWish(taskContext, gachaLogClient);
|
||||
TypedWishSummaryBuilder standardWishBuilder = new(standardContext);
|
||||
@@ -75,45 +62,6 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
Dictionary<Weapon, int> purpleWeaponCounter = [];
|
||||
Dictionary<Weapon, int> blueWeaponCounter = [];
|
||||
|
||||
if (isNeverHeldStatisticsItemVisible)
|
||||
{
|
||||
orangeAvatarCounter = context.IdAvatarMap.Values
|
||||
.Where(avatar => avatar.Quality == QualityType.QUALITY_ORANGE)
|
||||
.ToDictionary(avatar => avatar, _ => 0);
|
||||
purpleAvatarCounter = context.IdAvatarMap.Values
|
||||
.Where(avatar => avatar.Quality == QualityType.QUALITY_PURPLE)
|
||||
.ToDictionary(avatar => avatar, _ => 0);
|
||||
orangeWeaponCounter = context.IdWeaponMap.Values
|
||||
.Where(weapon => weapon.Quality == QualityType.QUALITY_ORANGE)
|
||||
.ToDictionary(weapon => weapon, _ => 0);
|
||||
|
||||
HashSet<Weapon> purpleWeapons = [];
|
||||
foreach (uint weaponId in PurpleStandardWeaponIdsSet)
|
||||
{
|
||||
purpleWeapons.Add(context.IdWeaponMap[weaponId]);
|
||||
}
|
||||
|
||||
foreach (GachaEvent gachaEvent in context.GachaEvents)
|
||||
{
|
||||
if (gachaEvent.Type is GachaType.ActivityWeapon)
|
||||
{
|
||||
foreach (uint weaponId in gachaEvent.UpPurpleList)
|
||||
{
|
||||
purpleWeapons.Add(context.IdWeaponMap[weaponId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<Weapon> blueWeapons = [];
|
||||
foreach (uint weaponId in BlueStandardWeaponIdsSet)
|
||||
{
|
||||
blueWeapons.Add(context.IdWeaponMap[weaponId]);
|
||||
}
|
||||
|
||||
purpleWeaponCounter = purpleWeapons.ToDictionary(weapon => weapon, _ => 0);
|
||||
blueWeaponCounter = blueWeapons.ToDictionary(weapon => weapon, _ => 0);
|
||||
}
|
||||
|
||||
// Pre group builders
|
||||
Dictionary<GachaType, List<HistoryWishBuilder>> historyWishBuilderMap = historyWishBuilders
|
||||
.GroupBy(b => b.ConfigType)
|
||||
|
||||
@@ -19,7 +19,7 @@ internal sealed class HutaoStatisticsFactory
|
||||
private readonly GachaEvent avatarEvent;
|
||||
private readonly GachaEvent avatarEvent2;
|
||||
private readonly GachaEvent weaponEvent;
|
||||
private readonly GachaEvent chronicledEvent;
|
||||
private readonly GachaEvent? chronicledEvent;
|
||||
|
||||
public HutaoStatisticsFactory(in HutaoStatisticsFactoryMetadataContext context)
|
||||
{
|
||||
@@ -32,7 +32,7 @@ internal sealed class HutaoStatisticsFactory
|
||||
avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityAvatar);
|
||||
avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.SpecialActivityAvatar);
|
||||
weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityWeapon);
|
||||
chronicledEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityCity);
|
||||
chronicledEvent = context.GachaEvents.SingleOrDefault(g => g.From < now && g.To > now && g.Type == GachaType.ActivityCity);
|
||||
}
|
||||
|
||||
public HutaoStatistics Create(GachaEventStatistics raw)
|
||||
@@ -42,7 +42,7 @@ internal sealed class HutaoStatisticsFactory
|
||||
AvatarEvent = CreateWishSummary(avatarEvent, raw.AvatarEvent),
|
||||
AvatarEvent2 = CreateWishSummary(avatarEvent2, raw.AvatarEvent2),
|
||||
WeaponEvent = CreateWishSummary(weaponEvent, raw.WeaponEvent),
|
||||
Chronicled = CreateWishSummary(chronicledEvent, raw.Chronicled),
|
||||
Chronicled = chronicledEvent is null ? null : CreateWishSummary(chronicledEvent, raw.Chronicled),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hutao.HutaoAsAService;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Snap.Hutao.Service.Hutao;
|
||||
|
||||
internal interface IHutaoAsAService
|
||||
{
|
||||
ValueTask<ObservableCollection<Web.Hutao.HutaoAsAService.Announcement>> GetHutaoAnnouncementCollectionAsync(CancellationToken token = default);
|
||||
ValueTask<ObservableCollection<Announcement>> GetHutaoAnnouncementCollectionAsync(CancellationToken token = default);
|
||||
}
|
||||
@@ -44,8 +44,6 @@
|
||||
<SelfContained>true</SelfContained>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSdkUndockedRegFreeWinRTInitialize>false</WindowsAppSdkUndockedRegFreeWinRTInitialize>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -153,6 +151,7 @@
|
||||
<None Remove="View\Control\DescParamComboBox.xaml" />
|
||||
<None Remove="View\Control\Elevation.xaml" />
|
||||
<None Remove="View\Control\HutaoStatisticsCard.xaml" />
|
||||
<None Remove="View\Control\HutaoStatisticsPanel.xaml" />
|
||||
<None Remove="View\Control\ItemIcon.xaml" />
|
||||
<None Remove="View\Control\LaunchGameResourceExpander.xaml" />
|
||||
<None Remove="View\Control\LoadingView.xaml" />
|
||||
@@ -305,8 +304,8 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -321,7 +320,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240404000" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240311000" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
|
||||
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.16.0">
|
||||
@@ -349,6 +348,14 @@
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Control\HutaoStatisticsPanel.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -52,13 +52,9 @@
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Right"
|
||||
Text="{Binding DisplayName}"/>
|
||||
<Viewbox
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Left"
|
||||
StretchDirection="DownOnly">
|
||||
<Viewbox Grid.Row="1" StretchDirection="DownOnly">
|
||||
<TextBlock
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{Binding FinishDescription}"/>
|
||||
</Viewbox>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<shcp:HorizontalEqualPanel
|
||||
x:Class="Snap.Hutao.View.Control.HutaoStatisticsPanel"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:shcp="using:Snap.Hutao.Control.Panel"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
mc:Ignorable="d">
|
||||
<shvc:HutaoStatisticsCard DataContext="{x:Bind Statistics.AvatarEvent}"/>
|
||||
<shvc:HutaoStatisticsCard DataContext="{x:Bind Statistics.AvatarEvent2}"/>
|
||||
<shvc:HutaoStatisticsCard DataContext="{x:Bind Statistics.WeaponEvent}"/>
|
||||
</shcp:HorizontalEqualPanel>
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Control.Panel;
|
||||
using Snap.Hutao.ViewModel.GachaLog;
|
||||
|
||||
namespace Snap.Hutao.View.Control;
|
||||
|
||||
[DependencyProperty("Statistics", typeof(HutaoStatistics), default, nameof(OnStatisticsChanged))]
|
||||
internal sealed partial class HutaoStatisticsPanel : HorizontalEqualPanel
|
||||
{
|
||||
public HutaoStatisticsPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (DataContext is HutaoStatistics statistics && statistics.Chronicled is { } chronicled)
|
||||
{
|
||||
Children.Add(new HutaoStatisticsCard
|
||||
{
|
||||
DataContext = chronicled,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnStatisticsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
HutaoStatisticsPanel panel = (HutaoStatisticsPanel)obj;
|
||||
|
||||
if (args.NewValue is HutaoStatistics { Chronicled: { } chronicled })
|
||||
{
|
||||
panel.Children.Add(new HutaoStatisticsCard
|
||||
{
|
||||
DataContext = chronicled,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<cwc:SwitchPresenter ContentTransitions="{ThemeResource EntranceThemeTransitions}" Value="{Binding State, Mode=OneWay}">
|
||||
<cwc:SwitchPresenter Value="{Binding State, Mode=OneWay}">
|
||||
<cwc:Case Value="{shcm:UInt32 Value=0}">
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<GridView
|
||||
@@ -125,11 +125,10 @@
|
||||
<StackPanel
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
Margin="1,0,0,5"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Text="Segoe Fluent Icons"/>
|
||||
<StackPanel
|
||||
Margin="0,8"
|
||||
@@ -156,10 +155,7 @@
|
||||
<Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentFontDescription2}"/>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentAfterInstallDescription}"/>
|
||||
<TextBlock
|
||||
Margin="1,32,0,5"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{Binding RuntimeOptions.WebView2Version}"/>
|
||||
<TextBlock>
|
||||
<Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentWebView2Description1}"/>
|
||||
@@ -173,78 +169,6 @@
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case Value="{shcm:UInt32 Value=3}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingHomeAnnouncementRegionHeader}"/>
|
||||
<ListView
|
||||
MinWidth="320"
|
||||
Margin="0,8,0,0"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding AppOptions.LazyRegions.Value}"
|
||||
SelectedItem="{Binding SelectedRegion, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
Opacity="0.7"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewGuideStepCommonSettingHint}"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case Value="{shcm:UInt32 Value=4}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingQualityHeader}"/>
|
||||
<ListView
|
||||
MinWidth="320"
|
||||
Margin="0,8,0,32"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding StaticResourceOptions.ImageQualities}"
|
||||
SelectedItem="{Binding StaticResourceOptions.ImageQuality, Mode=TwoWay}"/>
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingMinimumHeader}"/>
|
||||
<ListView
|
||||
MinWidth="320"
|
||||
Margin="0,8,0,32"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding StaticResourceOptions.ImageArchives}"
|
||||
SelectedItem="{Binding StaticResourceOptions.ImageArchive, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Margin="0,16,0,0" Text="{Binding StaticResourceOptions.SizeInformationText, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
|
||||
HorizontalTextAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingHint}"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case Value="{shcm:UInt32 Value=5}">
|
||||
<StackPanel Margin="32,0" HorizontalAlignment="Left">
|
||||
<TextBlock
|
||||
Margin="1,16,0,5"
|
||||
@@ -266,23 +190,12 @@
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<cwc:Segmented
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
SelectedIndex="{Binding State, Mode=TwoWay}">
|
||||
<!--
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepLanguage}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepDocument}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepEnvironment}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepCommonSetting}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResourceSetting}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResource}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
-->
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
</cwc:Segmented>
|
||||
<Button
|
||||
Command="{Binding NextOrCompleteCommand}"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
namespace Snap.Hutao.View.Guide;
|
||||
@@ -15,6 +14,6 @@ internal sealed partial class GuideView : UserControl
|
||||
public GuideView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this.ServiceProvider().GetRequiredService<GuideViewModel>();
|
||||
DataContext = Ioc.Default.GetRequiredService<GuideViewModel>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cw="using:CommunityToolkit.WinUI"
|
||||
xmlns:cwcont="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:cwconv="using:CommunityToolkit.WinUI.Converters"
|
||||
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"
|
||||
@@ -25,11 +24,6 @@
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
<cwconv:DoubleToObjectConverter x:Key="DoubleToOpacityConverter" GreaterThan="0">
|
||||
<cwconv:DoubleToObjectConverter.TrueValue>1.0</cwconv:DoubleToObjectConverter.TrueValue>
|
||||
<cwconv:DoubleToObjectConverter.FalseValue>0.4</cwconv:DoubleToObjectConverter.FalseValue>
|
||||
</cwconv:DoubleToObjectConverter>
|
||||
|
||||
<Flyout x:Key="HutaoCloudFlyout">
|
||||
<Grid>
|
||||
<mxi:Interaction.Behaviors>
|
||||
@@ -126,12 +120,12 @@
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudNotAllowed}"
|
||||
TextAlignment="Center"/>
|
||||
<cwcont:SettingsCard
|
||||
<cwc:SettingsCard
|
||||
Command="{Binding HutaoCloudViewModel.NavigateToSpiralAbyssRecordCommand}"
|
||||
Description="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudSpiralAbyssActivityDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudSpiralAbyssActivityHeader}"
|
||||
IsClickEnabled="True"/>
|
||||
<cwcont:SettingsCard
|
||||
<cwc:SettingsCard
|
||||
Command="{Binding HutaoCloudViewModel.NavigateToAfdianSkuCommand}"
|
||||
Description="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudAfdianPurchaseDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudAfdianPurchaseHeader}"
|
||||
@@ -216,7 +210,6 @@
|
||||
<shvc:ItemIcon
|
||||
Badge="{Binding Badge}"
|
||||
Icon="{Binding Icon}"
|
||||
Opacity="{Binding Count, Converter={StaticResource DoubleToOpacityConverter}}"
|
||||
Quality="{Binding Quality}"/>
|
||||
<Border
|
||||
HorizontalAlignment="Right"
|
||||
@@ -335,14 +328,14 @@
|
||||
HorizontalAlignment="Left"
|
||||
cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Grid HorizontalAlignment="Center" Style="{StaticResource GridCardStyle}">
|
||||
<cwcont:ConstrainedBox AspectRatio="1080:533">
|
||||
<cwc:ConstrainedBox AspectRatio="1080:533">
|
||||
<shci:CachedImage
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
Source="{Binding SelectedHistoryWish.BannerImage}"
|
||||
Stretch="UniformToFill"/>
|
||||
</cwcont:ConstrainedBox>
|
||||
</cwc:ConstrainedBox>
|
||||
<Border Grid.ColumnSpan="2" Background="{ThemeResource DarkOnlyOverlayMaskColorBrush}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -475,24 +468,15 @@
|
||||
Margin="16"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
IsLoading="{Binding HutaoCloudStatisticsViewModel.IsInitialized, Converter={StaticResource BoolNegationConverter}}"/>
|
||||
<Grid
|
||||
<shvc:HutaoStatisticsPanel
|
||||
Margin="16"
|
||||
ColumnSpacing="16"
|
||||
Spacing="16"
|
||||
Statistics="{Binding HutaoCloudStatisticsViewModel.Statistics}"
|
||||
Visibility="{Binding HutaoCloudStatisticsViewModel.IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding HutaoCloudStatisticsViewModel.OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shvc:HutaoStatisticsCard Grid.Column="0" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent}"/>
|
||||
<shvc:HutaoStatisticsCard Grid.Column="1" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent2}"/>
|
||||
<shvc:HutaoStatisticsCard Grid.Column="2" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponEvent}"/>
|
||||
<shvc:HutaoStatisticsCard Grid.Column="3" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.Chronicled}"/>
|
||||
</Grid>
|
||||
</shvc:HutaoStatisticsPanel>
|
||||
</Grid>
|
||||
</PivotItem>
|
||||
</Pivot>
|
||||
@@ -514,35 +498,35 @@
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageGachaLogHint}"/>
|
||||
<StackPanel Margin="0,24,0,0" Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<cwcont:SettingsCard
|
||||
<cwc:SettingsCard
|
||||
ActionIconToolTip="{shcm:ResourceString Name=ViewPageGachaLogRefreshAction}"
|
||||
Command="{Binding RefreshBySTokenCommand}"
|
||||
Description="{shcm:ResourceString Name=ViewPageGachaLogRefreshBySTokenDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageGachaLogRefreshBySToken}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
IsClickEnabled="True"/>
|
||||
<cwcont:SettingsCard
|
||||
<cwc:SettingsCard
|
||||
ActionIconToolTip="{shcm:ResourceString Name=ViewPageGachaLogRefreshAction}"
|
||||
Command="{Binding RefreshByWebCacheCommand}"
|
||||
Description="{shcm:ResourceString Name=ViewPageGachaLogRefreshByWebCacheDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageGachaLogRefreshByWebCache}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
IsClickEnabled="True"/>
|
||||
<cwcont:SettingsCard
|
||||
<cwc:SettingsCard
|
||||
ActionIconToolTip="{shcm:ResourceString Name=ViewPageGachaLogInputAction}"
|
||||
Command="{Binding RefreshByManualInputCommand}"
|
||||
Description="{shcm:ResourceString Name=ViewPageGachaLogRefreshByManualInputDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageGachaLogRefreshByManualInput}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
IsClickEnabled="True"/>
|
||||
<cwcont:SettingsCard
|
||||
<cwc:SettingsCard
|
||||
ActionIconToolTip="{shcm:ResourceString Name=ViewPageGachaLogImportAction}"
|
||||
Command="{Binding ImportFromUIGFJsonCommand}"
|
||||
Description="{shcm:ResourceString Name=ViewPageGachaLogImportDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageGachaLogImportHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
IsClickEnabled="True"/>
|
||||
<cwcont:SettingsCard
|
||||
<cwc:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageGachaLogRecoverFromHutaoCloudDescription}"
|
||||
FlyoutBase.AttachedFlyout="{StaticResource HutaoCloudFlyout}"
|
||||
Header="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloud}"
|
||||
@@ -553,7 +537,7 @@
|
||||
<shcb:ShowAttachedFlyoutAction/>
|
||||
</mxic:EventTriggerBehavior>
|
||||
</mxi:Interaction.Behaviors>
|
||||
</cwcont:SettingsCard>
|
||||
</cwc:SettingsCard>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -559,15 +559,6 @@
|
||||
OffContent="{shcm:ResourceString Name=ViewPageSettingEmptyHistoryVisibleOff}"
|
||||
OnContent="{shcm:ResourceString Name=ViewPageSettingEmptyHistoryVisibleOn}"/>
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageSettingNeverHeldItemVisibleDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageSettingNeverHeldItemVisibleHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch
|
||||
IsOn="{Binding AppOptions.IsNeverHeldStatisticsItemVisible, Mode=TwoWay}"
|
||||
OffContent="{shcm:ResourceString Name=ViewPageSettingEmptyHistoryVisibleOff}"
|
||||
OnContent="{shcm:ResourceString Name=ViewPageSettingEmptyHistoryVisibleOn}"/>
|
||||
</cwc:SettingsCard>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
@@ -26,5 +26,5 @@ internal sealed class HutaoStatistics
|
||||
/// <summary>
|
||||
/// 集录祈愿
|
||||
/// </summary>
|
||||
public HutaoWishSummary Chronicled { get; set; } = default!;
|
||||
public HutaoWishSummary? Chronicled { get; set; }
|
||||
}
|
||||
@@ -7,107 +7,105 @@ using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Factory.Progress;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using System.Collections.Frozen;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
/// <summary>
|
||||
/// 下载信息
|
||||
/// </summary>
|
||||
internal sealed class DownloadSummary : ObservableObject
|
||||
{
|
||||
private static readonly FrozenSet<string?> AllowedMediaTypes = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
"application/octet-stream",
|
||||
"application/zip",
|
||||
]);
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IImageCache imageCache;
|
||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private readonly string fileName;
|
||||
private readonly string fileUrl;
|
||||
private readonly IProgress<StreamCopyStatus> progress;
|
||||
|
||||
private readonly Progress<StreamCopyStatus> progress;
|
||||
private string description = SH.ViewModelWelcomeDownloadSummaryDefault;
|
||||
private double progressValue;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的下载信息
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
/// <param name="fileName">压缩文件名称</param>
|
||||
public DownloadSummary(IServiceProvider serviceProvider, string fileName)
|
||||
{
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
httpRequestMessageBuilderFactory = serviceProvider.GetRequiredService<IHttpRequestMessageBuilderFactory>();
|
||||
httpClient = serviceProvider.GetRequiredService<HttpClient>();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(serviceProvider.GetRequiredService<RuntimeOptions>().UserAgent);
|
||||
imageCache = serviceProvider.GetRequiredService<IImageCache>();
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(runtimeOptions.UserAgent);
|
||||
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
DisplayName = fileName;
|
||||
this.fileName = fileName;
|
||||
fileUrl = Web.HutaoEndpoints.StaticZip(fileName);
|
||||
|
||||
progress = serviceProvider.GetRequiredService<IProgressFactory>().CreateForMainThread<StreamCopyStatus>(UpdateProgressStatus);
|
||||
progress = new(UpdateProgressStatus);
|
||||
}
|
||||
|
||||
public string DisplayName { get => fileName; }
|
||||
/// <summary>
|
||||
/// 显示名称
|
||||
/// </summary>
|
||||
public string DisplayName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get => description; private set => SetProperty(ref description, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 进度值,最大1
|
||||
/// </summary>
|
||||
public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 异步下载并解压
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
public async ValueTask<bool> DownloadAndExtractAsync()
|
||||
{
|
||||
ILogger<DownloadSummary> logger = serviceProvider.GetRequiredService<ILogger<DownloadSummary>>();
|
||||
try
|
||||
{
|
||||
HttpRequestMessage message = httpRequestMessageBuilderFactory
|
||||
.Create()
|
||||
.SetRequestUri(fileUrl)
|
||||
.SetStaticResourceControlHeaders()
|
||||
.Get()
|
||||
.HttpRequestMessage;
|
||||
HttpResponseMessage response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||
|
||||
using (message)
|
||||
if (response.Content.Headers.ContentType?.MediaType is not ("application/octet-stream" or "application/zip"))
|
||||
{
|
||||
using (HttpResponseMessage response = await httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
if (!AllowedMediaTypes.Contains(response.Content.Headers.ContentType?.MediaType))
|
||||
{
|
||||
logger.LogWarning("Download Static Zip failed, Content-Type is {Type}", response.Content.Headers.ContentType);
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Description = SH.ViewModelWelcomeDownloadSummaryContentTypeNotMatch;
|
||||
return false;
|
||||
}
|
||||
logger.LogWarning("Download Static Zip failed, Content-Type is {Type}", response.Content.Headers.ContentType);
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Description = SH.ViewModelWelcomeDownloadSummaryContentTypeNotMatch;
|
||||
return false;
|
||||
}
|
||||
|
||||
long contentLength = response.Content.Headers.ContentLength ?? 0;
|
||||
logger.LogInformation("Begin download, size: {length}", Converters.ToFileSizeString(contentLength));
|
||||
using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
using (TempFileStream temp = new(FileMode.OpenOrCreate, FileAccess.ReadWrite))
|
||||
{
|
||||
await new StreamCopyWorker(content, temp, contentLength).CopyAsync(progress).ConfigureAwait(false);
|
||||
ExtractFiles(temp);
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ProgressValue = 1;
|
||||
Description = SH.ViewModelWelcomeDownloadSummaryComplete;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
long contentLength = response.Content.Headers.ContentLength ?? 0;
|
||||
logger.LogInformation("Begin download, length: {length}", contentLength);
|
||||
using (Stream content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
using (TempFileStream temp = new(FileMode.OpenOrCreate, FileAccess.ReadWrite))
|
||||
{
|
||||
await new StreamCopyWorker(content, temp, contentLength).CopyAsync(progress).ConfigureAwait(false);
|
||||
ExtractFiles(temp);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ProgressValue = 1;
|
||||
Description = SH.ViewModelWelcomeDownloadSummaryComplete;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Download static zip failed");
|
||||
|
||||
logger.LogError(ex, "Download Static Zip failed");
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Description = ex is HttpRequestException httpRequestEx
|
||||
? $"{SH.ViewModelWelcomeDownloadSummaryException} - [HTTP '{httpRequestEx.StatusCode:D}'] [Error '{httpRequestEx.HttpRequestError}']"
|
||||
Description = ex is HttpRequestException httpRequestException
|
||||
? $"{SH.ViewModelWelcomeDownloadSummaryException} - [HTTP '{httpRequestException.StatusCode:D}'] [Error '{httpRequestException.HttpRequestError}']"
|
||||
: ex.Message;
|
||||
return false;
|
||||
}
|
||||
@@ -116,7 +114,7 @@ internal sealed class DownloadSummary : ObservableObject
|
||||
private void UpdateProgressStatus(StreamCopyStatus status)
|
||||
{
|
||||
Description = $"{Converters.ToFileSizeString(status.BytesCopied)}/{Converters.ToFileSizeString(status.TotalBytes)}";
|
||||
ProgressValue = status.TotalBytes is 0 ? 0 : (double)status.BytesCopied / status.TotalBytes;
|
||||
ProgressValue = status.TotalBytes == 0 ? 0 : (double)status.BytesCopied / status.TotalBytes;
|
||||
}
|
||||
|
||||
private void ExtractFiles(Stream stream)
|
||||
|
||||
@@ -24,17 +24,7 @@ internal enum GuideState : uint
|
||||
Environment,
|
||||
|
||||
/// <summary>
|
||||
/// 正在查看常用设置
|
||||
/// </summary>
|
||||
CommonSetting,
|
||||
|
||||
/// <summary>
|
||||
/// 正在查看图像资源设置
|
||||
/// </summary>
|
||||
StaticResourceSetting,
|
||||
|
||||
/// <summary>
|
||||
/// 开始下载图像资源
|
||||
/// 开始下载资源
|
||||
/// </summary>
|
||||
StaticResourceBegin,
|
||||
|
||||
|
||||
@@ -6,9 +6,6 @@ using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using Snap.Hutao.Web.Hutao.Response;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
|
||||
@@ -23,15 +20,12 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly StaticResourceOptions staticResourceOptions;
|
||||
private readonly CultureOptions cultureOptions;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly AppOptions appOptions;
|
||||
|
||||
private string nextOrCompleteButtonText = SH.ViewModelGuideActionNext;
|
||||
private bool isNextOrCompleteButtonEnabled = true;
|
||||
private NameValue<CultureInfo>? selectedCulture;
|
||||
private NameValue<Region>? selectedRegion;
|
||||
private bool isTermOfServiceAgreed;
|
||||
private bool isPrivacyPolicyAgreed;
|
||||
private bool isIssueReportAgreed;
|
||||
@@ -42,7 +36,8 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
get
|
||||
{
|
||||
GuideState state = UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language);
|
||||
uint value = LocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, 0U);
|
||||
GuideState state = (GuideState)value;
|
||||
|
||||
if (state is GuideState.Document)
|
||||
{
|
||||
@@ -66,12 +61,12 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
(NextOrCompleteButtonText, IsNextOrCompleteButtonEnabled) = (SH.ViewModelGuideActionNext, true);
|
||||
}
|
||||
|
||||
return (uint)state;
|
||||
return value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, value);
|
||||
LocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, value);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
@@ -84,10 +79,6 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
|
||||
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
|
||||
|
||||
public AppOptions AppOptions { get => appOptions; }
|
||||
|
||||
public StaticResourceOptions StaticResourceOptions { get => staticResourceOptions; }
|
||||
|
||||
public NameValue<CultureInfo>? SelectedCulture
|
||||
{
|
||||
get => selectedCulture ??= CultureOptions.GetCurrentCultureForSelectionOrDefault();
|
||||
@@ -102,26 +93,13 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
public NameValue<Region>? SelectedRegion
|
||||
{
|
||||
get => selectedRegion ??= AppOptions.GetCurrentRegionForSelectionOrDefault();
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedRegion, value) && value is not null)
|
||||
{
|
||||
AppOptions.Region = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Agreement
|
||||
public bool IsTermOfServiceAgreed
|
||||
{
|
||||
get => isTermOfServiceAgreed; set
|
||||
{
|
||||
if (SetProperty(ref isTermOfServiceAgreed, value))
|
||||
{
|
||||
OnAgreementStateChanged();
|
||||
OnAgreeSateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +110,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
if (SetProperty(ref isPrivacyPolicyAgreed, value))
|
||||
{
|
||||
OnAgreementStateChanged();
|
||||
OnAgreeSateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +121,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
if (SetProperty(ref isIssueReportAgreed, value))
|
||||
{
|
||||
OnAgreementStateChanged();
|
||||
OnAgreeSateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,38 +132,27 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
{
|
||||
if (SetProperty(ref isOpenSourceLicenseAgreed, value))
|
||||
{
|
||||
OnAgreementStateChanged();
|
||||
OnAgreeSateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 下载信息
|
||||
/// </summary>
|
||||
public ObservableCollection<DownloadSummary>? DownloadSummaries
|
||||
{
|
||||
get => downloadSummaries;
|
||||
set => SetProperty(ref downloadSummaries, value);
|
||||
}
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
{
|
||||
HutaoInfrastructureClient hutaoInfrastructureClient = serviceProvider.GetRequiredService<HutaoInfrastructureClient>();
|
||||
HutaoResponse<StaticResourceSizeInformation> response = await hutaoInfrastructureClient.GetStaticSizeAsync().ConfigureAwait(false);
|
||||
if (response.IsOk())
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
StaticResourceOptions.SizeInformation = response.Data;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Command("NextOrCompleteCommand")]
|
||||
private void NextOrComplete()
|
||||
{
|
||||
++State;
|
||||
}
|
||||
|
||||
private void OnAgreementStateChanged()
|
||||
private void OnAgreeSateChanged()
|
||||
{
|
||||
IsNextOrCompleteButtonEnabled = IsTermOfServiceAgreed && IsPrivacyPolicyAgreed && IsIssueReportAgreed && IsOpenSourceLicenseAgreed;
|
||||
}
|
||||
@@ -206,7 +173,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
StaticResource.FulfillAll();
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Completed);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Completed);
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ internal static class StaticResource
|
||||
|
||||
private static readonly ApplicationDataCompositeValue DefaultResourceVersionMap = new()
|
||||
{
|
||||
// DO NOT MODIFY THIS MAP
|
||||
// DO NOT MIDIFY THIS MAP
|
||||
{ "AchievementIcon", 0 },
|
||||
{ "AvatarCard", 0 },
|
||||
{ "AvatarIcon", 0 },
|
||||
@@ -46,31 +46,31 @@ internal static class StaticResource
|
||||
|
||||
private static readonly ApplicationDataCompositeValue LatestResourceVersionMap = new()
|
||||
{
|
||||
{ "AchievementIcon", 2 },
|
||||
{ "AvatarCard", 2 },
|
||||
{ "AvatarIcon", 5 },
|
||||
{ "Bg", 3 },
|
||||
{ "ChapterIcon", 3 },
|
||||
{ "AchievementIcon", 1 },
|
||||
{ "AvatarCard", 1 },
|
||||
{ "AvatarIcon", 4 },
|
||||
{ "Bg", 2 },
|
||||
{ "ChapterIcon", 2 },
|
||||
{ "CodexMonster", 0 },
|
||||
{ "Costume", 2 },
|
||||
{ "EmotionIcon", 3 },
|
||||
{ "EquipIcon", 4 },
|
||||
{ "GachaAvatarIcon", 4 },
|
||||
{ "GachaAvatarImg", 4 },
|
||||
{ "GachaEquipIcon", 4 },
|
||||
{ "Costume", 1 },
|
||||
{ "EmotionIcon", 2 },
|
||||
{ "EquipIcon", 3 },
|
||||
{ "GachaAvatarIcon", 3 },
|
||||
{ "GachaAvatarImg", 3 },
|
||||
{ "GachaEquipIcon", 3 },
|
||||
{ "GcgCharAvatarIcon", 0 },
|
||||
{ "IconElement", 3 },
|
||||
{ "ItemIcon", 4 },
|
||||
{ "LoadingPic", 2 },
|
||||
{ "MonsterIcon", 3 },
|
||||
{ "MonsterSmallIcon", 2 },
|
||||
{ "NameCardIcon", 3 },
|
||||
{ "NameCardPic", 4 },
|
||||
{ "IconElement", 2 },
|
||||
{ "ItemIcon", 3 },
|
||||
{ "LoadingPic", 1 },
|
||||
{ "MonsterIcon", 2 },
|
||||
{ "MonsterSmallIcon", 1 },
|
||||
{ "NameCardIcon", 2 },
|
||||
{ "NameCardPic", 3 },
|
||||
{ "NameCardPicAlpha", 0 },
|
||||
{ "Property", 2 },
|
||||
{ "RelicIcon", 4 },
|
||||
{ "Skill", 4 },
|
||||
{ "Talent", 4 },
|
||||
{ "Property", 1 },
|
||||
{ "RelicIcon", 3 },
|
||||
{ "Skill", 3 },
|
||||
{ "Talent", 3 },
|
||||
};
|
||||
|
||||
public static void FulfillAll()
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
[Localization]
|
||||
internal enum StaticResourceArchive
|
||||
{
|
||||
[LocalizationKey(nameof(SH.ViewGuideStepStaticResourceSettingMinimumOff))]
|
||||
Full,
|
||||
|
||||
[LocalizationKey(nameof(SH.ViewGuideStepStaticResourceSettingMinimumOn))]
|
||||
Minimum,
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
internal static class StaticResourceHttpHeaderBuilderExtension
|
||||
{
|
||||
public static TBuilder SetStaticResourceControlHeaders<TBuilder>(this TBuilder builder)
|
||||
where TBuilder : IHttpHeadersBuilder<HttpHeaders>
|
||||
{
|
||||
return builder
|
||||
.SetHeader("x-quality", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw)}")
|
||||
.SetHeader("x-archive", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageArchive, StaticResourceArchive.Full)}");
|
||||
}
|
||||
|
||||
public static TBuilder SetStaticResourceControlHeadersIf<TBuilder>(this TBuilder builder, bool condition)
|
||||
where TBuilder : IHttpHeadersBuilder<HttpHeaders>
|
||||
{
|
||||
return condition ? builder.SetStaticResourceControlHeaders() : builder;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Common;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed class StaticResourceOptions : ObservableObject
|
||||
{
|
||||
private readonly List<NameValue<StaticResourceQuality>> imageQualities = CollectionsNameValue.FromEnum<StaticResourceQuality>(q => q.GetLocalizedDescription());
|
||||
private readonly List<NameValue<StaticResourceArchive>> imageArchives = CollectionsNameValue.FromEnum<StaticResourceArchive>(a => a.GetLocalizedDescription());
|
||||
|
||||
private NameValue<StaticResourceQuality>? imageQuality;
|
||||
private NameValue<StaticResourceArchive>? imageArchive;
|
||||
private string? sizeInformationText;
|
||||
|
||||
private StaticResourceSizeInformation? sizeInformation;
|
||||
|
||||
public List<NameValue<StaticResourceQuality>> ImageQualities { get => imageQualities; }
|
||||
|
||||
public NameValue<StaticResourceQuality>? ImageQuality
|
||||
{
|
||||
get => imageQuality ??= ImageQualities.First(q => q.Value == UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw));
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref imageQuality, value) && value is not null)
|
||||
{
|
||||
UnsafeLocalSetting.Set(SettingKeys.StaticResourceImageQuality, value.Value);
|
||||
UpdateSizeInformationText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<NameValue<StaticResourceArchive>> ImageArchives { get => imageArchives; }
|
||||
|
||||
public NameValue<StaticResourceArchive>? ImageArchive
|
||||
{
|
||||
get => imageArchive ??= ImageArchives.First(a => a.Value == UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageArchive, StaticResourceArchive.Full));
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref imageArchive, value) && value is not null)
|
||||
{
|
||||
UnsafeLocalSetting.Set(SettingKeys.StaticResourceImageArchive, value.Value);
|
||||
UpdateSizeInformationText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public StaticResourceSizeInformation? SizeInformation
|
||||
{
|
||||
get => sizeInformation;
|
||||
set
|
||||
{
|
||||
sizeInformation = value;
|
||||
UpdateSizeInformationText();
|
||||
}
|
||||
}
|
||||
|
||||
public string? SizeInformationText { get => sizeInformationText; set => SetProperty(ref sizeInformationText, value); }
|
||||
|
||||
private void UpdateSizeInformationText()
|
||||
{
|
||||
if (SizeInformation is not null)
|
||||
{
|
||||
long result = (ImageQuality?.Value, ImageArchive?.Value) switch
|
||||
{
|
||||
(StaticResourceQuality.Raw, StaticResourceArchive.Full) => SizeInformation.RawFull,
|
||||
(StaticResourceQuality.Raw, StaticResourceArchive.Minimum) => SizeInformation.RawMinimum,
|
||||
(StaticResourceQuality.High, StaticResourceArchive.Full) => SizeInformation.HighFull,
|
||||
(StaticResourceQuality.High, StaticResourceArchive.Minimum) => SizeInformation.HighMinimum,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
SizeInformationText = SH.FormatViewGuideStaticResourceDownloadSize(Converters.ToFileSizeString(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
[Localization]
|
||||
internal enum StaticResourceQuality
|
||||
{
|
||||
[LocalizationKey(nameof(SH.ViewModelGuideStaticResourceQualityRaw))]
|
||||
Raw,
|
||||
|
||||
[LocalizationKey(nameof(SH.ViewModelGuideStaticResourceQualityHigh))]
|
||||
High,
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.Announcement;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.View.Card;
|
||||
using Snap.Hutao.View.Card.Primitive;
|
||||
|
||||
@@ -224,7 +224,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
private static void ResetStaticResource()
|
||||
{
|
||||
StaticResource.FailAll();
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
|
||||
[Command("ResetGuideStateCommand")]
|
||||
private static void ResetGuideState()
|
||||
{
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language);
|
||||
}
|
||||
|
||||
[Command("ExceptionCommand")]
|
||||
|
||||
@@ -17,16 +17,6 @@ internal sealed partial class HutaoInfrastructureClient
|
||||
private readonly ILogger<HutaoInfrastructureClient> logger;
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
public async ValueTask<HutaoResponse<StaticResourceSizeInformation>> GetStaticSizeAsync(CancellationToken token = default)
|
||||
{
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(HutaoEndpoints.StaticSize)
|
||||
.Get();
|
||||
|
||||
HutaoResponse<StaticResourceSizeInformation>? resp = await builder.TryCatchSendAsync<HutaoResponse<StaticResourceSizeInformation>>(httpClient, logger, token).ConfigureAwait(false);
|
||||
return Web.Response.Response.DefaultIfNull(resp);
|
||||
}
|
||||
|
||||
public async ValueTask<HutaoResponse<IPInformation>> GetIPInformationAsync(CancellationToken token = default)
|
||||
{
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao;
|
||||
|
||||
internal sealed partial class StaticResourceSizeInformation
|
||||
{
|
||||
[JsonPropertyName("raw_full")]
|
||||
public long RawFull { get; set; }
|
||||
|
||||
[JsonPropertyName("raw_minimum")]
|
||||
public long RawMinimum { get; set; }
|
||||
|
||||
[JsonPropertyName("tiny_full")]
|
||||
public long HighFull { get; set; }
|
||||
|
||||
[JsonPropertyName("tiny_minimum")]
|
||||
public long HighMinimum { get; set; }
|
||||
}
|
||||
@@ -270,8 +270,6 @@ internal static class HutaoEndpoints
|
||||
{
|
||||
return $"{ApiSnapGenshinStaticZip}/{fileName}.zip";
|
||||
}
|
||||
|
||||
public const string StaticSize = $"{ApiSnapGenshin}/static/size";
|
||||
#endregion
|
||||
|
||||
#region Wallpaper
|
||||
|
||||
@@ -35,7 +35,8 @@ internal static class HttpContentBuilderExtension
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public static T SetFormUrlEncodedContent<T>(this T builder, IEnumerable<KeyValuePair<string, string>> content)
|
||||
public static T SetFormUrlEncodedContent<T>(
|
||||
this T builder, IEnumerable<KeyValuePair<string, string>> content)
|
||||
where T : IHttpContentBuilder
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
@@ -41,6 +41,12 @@ internal static partial class HttpHeadersBuilderExtension
|
||||
return builder.ConfigureHeaders<TBuilder, THeaders>(headers => headers.Add(name, value));
|
||||
}
|
||||
|
||||
public static T SetReferer<T>(this T builder, string referer)
|
||||
where T : IHttpHeadersBuilder<HttpHeaders>
|
||||
{
|
||||
return builder.SetHeader("Referer", referer);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public static T SetHeader<T>(this T builder, string name)
|
||||
where T : IHttpHeadersBuilder<HttpHeaders>
|
||||
@@ -74,12 +80,6 @@ internal static partial class HttpHeadersBuilderExtension
|
||||
.AddHeader<TBuilder, THeaders>(name, value);
|
||||
}
|
||||
|
||||
public static T SetReferer<T>(this T builder, string referer)
|
||||
where T : IHttpHeadersBuilder<HttpHeaders>
|
||||
{
|
||||
return builder.SetHeader("Referer", referer);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public static T RemoveHeader<T>(this T builder, params string?[]? names)
|
||||
where T : IHttpHeadersBuilder<HttpHeaders>
|
||||
|
||||
@@ -12,7 +12,7 @@ internal static class HttpHeadersExtension
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
// We have to work around the .NET API a little bit. See the comment below for details.
|
||||
values ??= [];
|
||||
values ??= Enumerable.Empty<string?>();
|
||||
values = values.Where(v => v is not null);
|
||||
|
||||
if (values.Any())
|
||||
|
||||
Reference in New Issue
Block a user