Compare commits

..

13 Commits

Author SHA1 Message Date
DismissedLight
848392f8d4 prevent user service capture scoped app db context 2022-10-30 15:19:21 +08:00
DismissedLight
62d0fb5d05 launch game phase 1 2022-10-28 14:59:31 +08:00
DismissedLight
7a99c44b29 collocation for wiki avatar 2022-10-25 15:47:12 +08:00
DismissedLight
792a701183 fix view model scope 2022-10-25 13:12:50 +08:00
DismissedLight
fa19f7e817 add api cache 2022-10-24 16:12:30 +08:00
DismissedLight
bf5fcb70f8 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2022-10-21 16:40:45 +08:00
DismissedLight
fda642b72f hutao api v2 page 2022-10-21 16:40:10 +08:00
Masterain
fa650a95c5 Update PublishDistribution.yml 2022-10-18 12:23:53 -07:00
Masterain
02fae69d1e Create PublishDistribution.yml 2022-10-18 12:05:43 -07:00
DismissedLight
76800de6ee separate primary and secondary properties 2022-10-16 21:53:40 +08:00
DismissedLight
72b660119f support homa api 2022-10-16 13:10:06 +08:00
DismissedLight
67a1d5dc74 update to hutao api v2 2022-10-13 21:14:57 +08:00
DismissedLight
6e6d125814 update to hoyolab 2.38.1 2022-10-10 18:55:51 +08:00
228 changed files with 5839 additions and 1766 deletions

View File

@@ -0,0 +1,39 @@
name: PublishDistribution
on:
release:
types: [published]
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
Publish:
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout Repo
uses: actions/checkout@v3
# Download Publish.zip
- name: Download Release
uses: robinraju/release-downloader@v1.5
with:
repository: "DGP-Studio/Snap.Hutao"
latest: true
fileName: "*.zip"
out-file-path: ./release-download
# Upload to OD21 (Testing)
- name: Upload OD21
env:
RCCONF: ${{ secrets.RCCONF }}
run: |
curl https://rclone.org/install.sh | sudo bash
mkdir -p ~/.config/rclone/
cat << EOF > ~/.config/rclone/rclone.conf
$RCCONF
EOF
rclone copy ./release-download/* dgpODCN:/snaphutao/Releases/

View File

@@ -22,6 +22,7 @@ public class InjectionGenerator : ISourceGenerator
{
private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Singleton";
private const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Transient";
private const string InjectAsScopedName = "Snap.Hutao.Core.DependencyInjection.Annotation.InjectAs.Scoped";
/// <inheritdoc/>
public void Initialize(GeneratorInitializationContext context)
@@ -97,8 +98,11 @@ internal static partial class ServiceCollectionExtensions
case InjectAsTransientName:
lineBuilder.Append(@" services.AddTransient(");
break;
case InjectAsScopedName:
lineBuilder.Append(@" services.AddScoped(");
break;
default:
throw new InvalidOperationException($"非法的InjectAs值: [{injectAsName}]");
throw new InvalidOperationException($"非法的 InjectAs 值: [{injectAsName}]");
}
if (arguments.Length == 2)

View File

@@ -21,7 +21,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -2,7 +2,10 @@
x:Class="Snap.Hutao.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls">
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
xmlns:shvc="using:Snap.Hutao.View.Converter">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -13,21 +16,36 @@
<!--Modify Window title bar color-->
<StaticResource x:Key="WindowCaptionBackground" ResourceKey="ControlFillColorTransparentBrush"/>
<StaticResource x:Key="WindowCaptionBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
<!--Page Transparent Background-->
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/>
<!--IconFont-->
<FontFamily x:Key="SymbolThemeFontFamily">ms-appx:///Resource/Font/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
<!--InfoBar Resource-->
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
<!--Pivot Resource-->
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
<!--CornerRadius-->
<CornerRadius x:Key="CompatCornerRadius">6</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusTop">6,6,0,0</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusRight">0,6,6,0</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusBottom">0,0,6,6</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
<!--Converters-->
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
<shmmc:DescParamDescriptor x:Key="DescParamDescriptor"/>
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
<shmmc:GachaAvatarImgConverter x:Key="GachaAvatarImgConverter"/>
<shmmc:GachaAvatarIconConverter x:Key="GachaAvatarIconConverter"/>
<shmmc:ItemIconConverter x:Key="ItemIconConverter"/>
<shmmc:PropertyInfoDescriptor x:Key="PropertyDescriptor"/>
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -9,6 +9,7 @@ using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Core.Threading;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.AppCenter;
using Snap.Hutao.Service.Metadata;
using System.Diagnostics;
using Windows.Storage;
@@ -27,13 +28,14 @@ public partial class App : Application
/// Initializes the singleton application object.
/// </summary>
/// <param name="logger">日志器</param>
public App(ILogger<App> logger)
/// <param name="appCenter">App Center</param>
public App(ILogger<App> logger, AppCenter appCenter)
{
// load app resource
InitializeComponent();
this.logger = logger;
_ = new ExceptionRecorder(this, logger);
_ = new ExceptionRecorder(this, logger, appCenter);
}
/// <inheritdoc/>
@@ -52,11 +54,17 @@ public partial class App : Application
logger.LogInformation(EventIds.CommonLog, "Snap Hutao : {version}", CoreEnvironment.Version);
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", ApplicationData.Current.TemporaryFolder.Path);
JumpListHelper.ConfigAsync().SafeForget(logger);
Ioc.Default
.GetRequiredService<IMetadataService>()
.ImplictAs<IMetadataInitializer>()?
.InitializeInternalAsync()
.SafeForget(logger);
Ioc.Default
.GetRequiredService<AppCenter>()
.Initialize();
}
else
{

View File

@@ -56,6 +56,11 @@ public class AppDbContext : DbContext
/// </summary>
public DbSet<AvatarInfo> AvatarInfos { get; set; } = default!;
/// <summary>
/// 游戏内账号
/// </summary>
public DbSet<GameAccount> GameAccounts { get; set; } = default!;
/// <summary>
/// 构造一个临时的应用程序数据库上下文
/// </summary>

View File

@@ -55,4 +55,4 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
{
AssociatedObject.Height = (double)AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
}
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Behaviors;
using Microsoft.UI.Xaml;
using Snap.Hutao.Core;
namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// 按给定比例自动调整高度的行为
/// </summary>
internal class AutoWidthBehavior : BehaviorBase<FrameworkElement>
{
private static readonly DependencyProperty TargetWidthProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetWidth), 320D);
private static readonly DependencyProperty TargetHeightProperty = Property<AutoWidthBehavior>.Depend(nameof(TargetHeight), 1024D);
/// <summary>
/// 目标宽度
/// </summary>
public double TargetWidth
{
get => (double)GetValue(TargetWidthProperty);
set => SetValue(TargetWidthProperty, value);
}
/// <summary>
/// 目标高度
/// </summary>
public double TargetHeight
{
get => (double)GetValue(TargetHeightProperty);
set => SetValue(TargetHeightProperty, value);
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
UpdateElementWidth();
AssociatedObject.SizeChanged += OnSizeChanged;
}
/// <inheritdoc/>
protected override void OnDetaching()
{
AssociatedObject.SizeChanged -= OnSizeChanged;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateElementWidth();
}
private void UpdateElementWidth()
{
AssociatedObject.Width = (double)AssociatedObject.Height * (TargetWidth / TargetHeight);
}
}

View File

@@ -24,7 +24,7 @@ internal class I18NExtension : MarkupExtension
static I18NExtension()
{
string currentName = CultureInfo.CurrentUICulture.Name;
Type? languageType = ((IDictionary<string, Type>)TranslationMap).GetValueOrDefault2(currentName, typeof(LanguagezhCN));
Type? languageType = TranslationMap.GetValueOrDefault2(currentName, typeof(LanguagezhCN));
Translation = (ITranslation)Activator.CreateInstance(languageType!)!;
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
namespace Snap.Hutao.Control.Markup;

View File

@@ -4,7 +4,6 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core;
using Snap.Hutao.Web.Hutao.Model;
namespace Snap.Hutao.Control.Panel;
@@ -28,8 +27,8 @@ public sealed partial class PanelSelector : UserControl
/// </summary>
public string Current
{
get { return (string)GetValue(CurrentProperty); }
set { SetValue(CurrentProperty, value); }
get => (string)GetValue(CurrentProperty);
set => SetValue(CurrentProperty, value);
}
private void SplitButtonLoaded(object sender, RoutedEventArgs e)

View File

@@ -11,6 +11,9 @@ namespace Snap.Hutao.Control;
/// <summary>
/// 表示支持取消加载的异步页面
/// 在被导航到其他页面前触发取消异步通知
/// <para/>
/// InitializeWith{T}();
/// InitializeComponent();
/// </summary>
public class ScopedPage : Page
{
@@ -25,9 +28,6 @@ public class ScopedPage : Page
serviceScope = Ioc.Default.CreateScope();
}
/// <inheritdoc cref="IServiceScope.ServiceProvider"/>
public IServiceProvider ServiceProvider { get => serviceScope.ServiceProvider; }
/// <summary>
/// 初始化
/// </summary>
@@ -35,7 +35,7 @@ public class ScopedPage : Page
public void InitializeWith<TViewModel>()
where TViewModel : class, ISupportCancellation
{
ISupportCancellation viewModel = ServiceProvider.GetRequiredService<TViewModel>();
ISupportCancellation viewModel = serviceScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewLoadingCancellationTokenSource.Token;
DataContext = viewModel;
}

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Extension;
using Snap.Hutao.Core.Threading;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
@@ -23,6 +23,8 @@ public abstract class CacheBase<T>
{
private readonly SemaphoreSlim cacheFolderSemaphore = new(1);
private readonly ILogger logger;
// violate di rule
private readonly HttpClient httpClient;
private StorageFolder? baseFolder;

View File

@@ -0,0 +1,63 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text;
namespace Snap.Hutao.Core;
/// <summary>
/// 命令行建造器
/// </summary>
public class CommandLineBuilder
{
private const char WhiteSpace = ' ';
private readonly Dictionary<string, string?> options = new();
/// <summary>
/// 当符合条件时添加参数
/// </summary>
/// <param name="name">参数名称</param>
/// <param name="condition">条件</param>
/// <param name="value">值</param>
/// <returns>命令行建造器</returns>
public CommandLineBuilder AppendIf(string name, bool condition, object? value = null)
{
return condition ? Append(name, value) : this;
}
/// <summary>
/// 添加参数
/// </summary>
/// <param name="name">参数名称</param>
/// <param name="value">值</param>
/// <returns>命令行建造器</returns>
public CommandLineBuilder Append(string name, object? value = null)
{
options.Add(name, value?.ToString());
return this;
}
/// <inheritdoc cref="ToString"/>
public string Build()
{
return ToString();
}
/// <inheritdoc/>
public override string ToString()
{
StringBuilder s = new();
foreach ((string key, string? value) in options)
{
s.Append(WhiteSpace);
s.Append(key);
if (!string.IsNullOrEmpty(value))
{
s.Append(WhiteSpace);
s.Append(value);
}
}
return s.ToString();
}
}

View File

@@ -1,7 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Win32;
using Snap.Hutao.Extension;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using Windows.ApplicationModel;
@@ -12,12 +15,15 @@ namespace Snap.Hutao.Core;
/// </summary>
internal static class CoreEnvironment
{
private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\";
private const string MachineGuidValue = "MachineGuid";
// 计算过程https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd
/// <summary>
/// 动态密钥1的盐
/// </summary>
public const string DynamicSecret1Salt = "Qqx8cyv7kuyD8fTw11SmvXSFHp7iZD29";
public const string DynamicSecret1Salt = "yUZ3s0Sna1IrSNfk29Vo6vRapdOyqyhB";
/// <summary>
/// 动态密钥2的盐
@@ -32,7 +38,7 @@ internal static class CoreEnvironment
/// <summary>
/// 米游社 Rpc 版本
/// </summary>
public const string HoyolabXrpcVersion = "2.37.1";
public const string HoyolabXrpcVersion = "2.38.1";
/// <summary>
/// 标准UA
@@ -49,6 +55,11 @@ internal static class CoreEnvironment
/// </summary>
public static readonly string HoyolabDeviceId;
/// <summary>
/// AppCenter 设备Id
/// </summary>
public static readonly string AppCenterDeviceId;
/// <summary>
/// 默认的Json序列化选项
/// </summary>
@@ -67,5 +78,15 @@ internal static class CoreEnvironment
// simply assign a random guid
HoyolabDeviceId = Guid.NewGuid().ToString();
AppCenterDeviceId = GetUniqueUserID();
}
private static string GetUniqueUserID()
{
string userName = Environment.UserName;
object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName);
byte[] bytes = Encoding.UTF8.GetBytes($"{userName}{machineGuid}");
byte[] hash = MD5.Create().ComputeHash(bytes);
return System.Convert.ToHexString(hash);
}
}

View File

@@ -14,9 +14,8 @@ namespace Snap.Hutao.Core.Database;
/// <typeparam name="TMessage">消息的类型</typeparam>
internal class DbCurrent<TEntity, TMessage>
where TEntity : class, ISelectable
where TMessage : Message.ValueChangedMessage<TEntity>
where TMessage : Message.ValueChangedMessage<TEntity>, new()
{
private readonly DbContext dbContext;
private readonly DbSet<TEntity> dbSet;
private readonly IMessenger messenger;
@@ -25,12 +24,11 @@ internal class DbCurrent<TEntity, TMessage>
/// <summary>
/// 构造一个新的数据库当前项
/// </summary>
/// <param name="dbContext">数据库上下文</param>
/// <param name="dbSet">数据集</param>
/// <param name="messenger">消息器</param>
public DbCurrent(DbContext dbContext, DbSet<TEntity> dbSet, IMessenger messenger)
///
public DbCurrent(DbSet<TEntity> dbSet, IMessenger messenger)
{
this.dbContext = dbContext;
this.dbSet = dbSet;
this.messenger = messenger;
}
@@ -55,96 +53,18 @@ internal class DbCurrent<TEntity, TMessage>
if (current != null)
{
current.IsSelected = false;
dbSet.Update(current);
dbContext.SaveChanges();
dbSet.UpdateAndSave(current);
}
}
TMessage message = (TMessage)Activator.CreateInstance(typeof(TMessage), current, value)!;
TMessage message = new() { OldValue = current, NewValue = value };
current = value;
if (current != null)
{
current.IsSelected = true;
dbSet.Update(current);
dbContext.SaveChanges();
}
messenger.Send(message);
}
}
}
/// <summary>
/// 数据库当前项
/// 简化对数据库中选中项的管理
/// </summary>
/// <typeparam name="TObservable">绑定类型</typeparam>
/// <typeparam name="TEntity">实体的类型</typeparam>
/// <typeparam name="TMessage">消息的类型</typeparam>
[SuppressMessage("", "SA1402")]
internal class DbCurrent<TObservable, TEntity, TMessage>
where TObservable : class
where TEntity : class, ISelectable
where TMessage : Message.ValueChangedMessage<TObservable>
{
private readonly DbContext dbContext;
private readonly DbSet<TEntity> dbSet;
private readonly IMessenger messenger;
private readonly Func<TObservable, TEntity> selector;
private TObservable? current;
/// <summary>
/// 构造一个新的数据库当前项
/// </summary>
/// <param name="dbContext">数据库上下文</param>
/// <param name="dbSet">数据集</param>
/// <param name="selector">选择器</param>
/// <param name="messenger">消息器</param>
public DbCurrent(DbContext dbContext, DbSet<TEntity> dbSet, Func<TObservable, TEntity> selector, IMessenger messenger)
{
this.dbContext = dbContext;
this.dbSet = dbSet;
this.selector = selector;
this.messenger = messenger;
}
/// <summary>
/// 当前选中的项
/// </summary>
public TObservable? Current
{
get => current;
set
{
// prevent useless sets
if (current == value)
{
return;
}
// only update when not processing a deletion
if (value != null)
{
if (current != null)
{
TEntity entity = selector(current);
entity.IsSelected = false;
dbSet.Update(entity);
dbContext.SaveChanges();
}
}
TMessage message = (TMessage)Activator.CreateInstance(typeof(TMessage), current, value)!;
current = value;
if (current != null)
{
TEntity entity = selector(current);
entity.IsSelected = true;
dbSet.Update(entity);
dbContext.SaveChanges();
dbSet.UpdateAndSave(current);
}
messenger.Send(message);

View File

@@ -0,0 +1,94 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Snap.Hutao.Core.Database;
/// <summary>
/// 数据库集合上下文
/// </summary>
public static class DbSetExtension
{
/// <summary>
/// 获取对应的数据库上下文
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="dbSet">数据库集</param>
/// <returns>对应的数据库上下文</returns>
public static DbContext Context<TEntity>(this DbSet<TEntity> dbSet)
where TEntity : class
{
return dbSet.GetService<ICurrentDbContext>().Context;
}
/// <summary>
/// 获取或添加一个对应的实体
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="dbSet">数据库集</param>
/// <param name="predicate">谓词</param>
/// <param name="entityFactory">实体工厂</param>
/// <param name="added">是否添加</param>
/// <returns>实体</returns>
public static TEntity SingleOrAdd<TEntity>(this DbSet<TEntity> dbSet, Func<TEntity, bool> predicate, Func<TEntity> entityFactory, out bool added)
where TEntity : class
{
added = false;
TEntity? entry = dbSet.SingleOrDefault(predicate);
if (entry == null)
{
entry = entityFactory();
dbSet.Add(entry);
dbSet.Context().SaveChanges();
added = true;
}
return entry;
}
/// <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
{
dbSet.Add(entity);
return dbSet.Context().SaveChanges();
}
/// <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
{
dbSet.Remove(entity);
return dbSet.Context().SaveChanges();
}
/// <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
{
dbSet.Update(entity);
return dbSet.Context().SaveChanges();
}
}

View File

@@ -17,4 +17,9 @@ public enum InjectAs
/// 指示应注册为短期对象
/// </summary>
Transient,
/// <summary>
/// 指示应注册为范围对象
/// </summary>
Scoped,
}

View File

@@ -36,6 +36,7 @@ internal static partial class IocHttpClientConfiguration
{
client.Timeout = Timeout.InfiniteTimeSpan;
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabUA);
client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
client.DefaultRequestHeaders.Add("x-rpc-app_version", CoreEnvironment.HoyolabXrpcVersion);
client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);

View File

@@ -2,10 +2,8 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.Logging;
using System.Diagnostics;
using System.IO;
using Snap.Hutao.Service.AppCenter;
namespace Snap.Hutao.Core.Exception;
@@ -15,15 +13,18 @@ namespace Snap.Hutao.Core.Exception;
internal class ExceptionRecorder
{
private readonly ILogger logger;
private readonly AppCenter appCenter;
/// <summary>
/// 构造一个新的异常记录器
/// </summary>
/// <param name="application">应用程序</param>
/// <param name="logger">日志器</param>
public ExceptionRecorder(Application application, ILogger logger)
/// <param name="appCenter">App Center</param>
public ExceptionRecorder(Application application, ILogger logger, AppCenter appCenter)
{
this.logger = logger;
this.appCenter = appCenter;
application.UnhandledException += OnAppUnhandledException;
application.DebugSettings.BindingFailed += OnXamlBindingFailed;
@@ -31,9 +32,7 @@ internal class ExceptionRecorder
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
// string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
// string fileName = $"ex-{DateTimeOffset.Now:yyyyMMddHHmmssffff}.txt";
// File.WriteAllText(Path.Combine(path, fileName), $"{e.Exception}\r\n{e.Exception.StackTrace}");
appCenter.TrackCrash(e.Exception);
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
foreach (ILoggerProvider provider in Ioc.Default.GetRequiredService<IEnumerable<ILoggerProvider>>())

View File

@@ -0,0 +1,30 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.IO.Ini;
/// <summary>
/// Ini 注释
/// </summary>
internal class IniComment : IniElement
{
/// <summary>
/// 构造一个新的 Ini 注释
/// </summary>
/// <param name="comment">注释</param>
public IniComment(string comment)
{
Comment = comment;
}
/// <summary>
/// 注释
/// </summary>
public string Comment { get; set; }
/// <inheritdoc/>
public override string ToString()
{
return $";{Comment}";
}
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.IO.Ini;
/// <summary>
/// Ini 元素
/// </summary>
internal abstract class IniElement
{
/// <summary>
/// 将当前元素转换到等价的字符串表示
/// </summary>
/// <returns>字符串</returns>
public new abstract string ToString();
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.IO.Ini;
/// <summary>
/// Ini 参数
/// </summary>
internal class IniParameter : IniElement
{
/// <summary>
/// Ini 参数
/// </summary>
/// <param name="key">键</param>
/// <param name="value">值</param>
public IniParameter(string key, string value)
{
Key = key;
Value = value;
}
/// <summary>
/// 键
/// </summary>
public string Key { get; set; }
/// <summary>
/// 值
/// </summary>
public string Value { get; set; }
/// <inheritdoc/>
public override string ToString()
{
return $"{Key}={Value}";
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.IO.Ini;
/// <summary>
/// Ini 节
/// </summary>
internal class IniSection : IniElement
{
/// <summary>
/// 构造一个新的Ini 节
/// </summary>
/// <param name="name">名称</param>
/// <param name="elements">元素</param>
public IniSection(string name)
{
Name = name;
}
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <inheritdoc/>
public override string ToString()
{
return $"[{Name}]";
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO;
namespace Snap.Hutao.Core.IO.Ini;
/// <summary>
/// Ini 序列化器
/// </summary>
internal static class IniSerializer
{
/// <summary>
/// 异步反序列化
/// </summary>
/// <param name="fileStream">文件流</param>
/// <returns>Ini 元素集合</returns>
public static IEnumerable<IniElement> Deserialize(FileStream fileStream)
{
using (TextReader reader = new StreamReader(fileStream))
{
while (reader.ReadLine() is string line)
{
if (line.Length > 0)
{
if (line[0] == '[')
{
yield return new IniSection(line[1..^1]);
}
if (line[0] == ';')
{
yield return new IniComment(line[1..]);
}
if (line.IndexOf('=') > 0)
{
string[] parameters = line.Split('=', 2);
yield return new IniParameter(parameters[0], parameters[1]);
}
}
continue;
}
}
}
}

View File

@@ -21,4 +21,4 @@ internal class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEnumerabl
{
writer.WriteStringValue(string.Join(',', value));
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.LifeCycle;
using Windows.UI.StartScreen;
namespace Snap.Hutao.Core;
/// <summary>
/// 跳转列表帮助类
/// </summary>
public static class JumpListHelper
{
/// <summary>
/// 异步配置跳转列表
/// </summary>
/// <returns>任务</returns>
public static async Task ConfigAsync()
{
if (JumpList.IsSupported())
{
JumpList list = await JumpList.LoadCurrentAsync();
list.Items.Clear();
JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, "启动游戏");
launchGameItem.GroupName = "快捷操作";
launchGameItem.Logo = new("ms-appx:///Resource/Icon/UI_GuideIcon_PlayMethod.png");
list.Items.Add(launchGameItem);
await list.SaveAsync();
}
}
}

View File

@@ -3,7 +3,6 @@
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core.Threading;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Navigation;
@@ -14,6 +13,11 @@ namespace Snap.Hutao.Core.LifeCycle;
/// </summary>
internal static class Activation
{
/// <summary>
/// 启动游戏启动参数
/// </summary>
public const string LaunchGame = "LaunchGame";
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
/// <summary>
@@ -45,16 +49,36 @@ internal static class Activation
private static async Task HandleActivationCoreAsync(AppActivationArguments args)
{
_ = Ioc.Default.GetRequiredService<MainWindow>();
string argument = string.Empty;
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
await infoBarService.WaitInitializationAsync().ConfigureAwait(false);
if (args.Kind == ExtendedActivationKind.Launch)
{
if (args.TryGetLaunchActivatedArgument(out string? arguments))
{
argument = arguments;
}
}
switch (argument)
{
case "":
{
_ = Ioc.Default.GetRequiredService<MainWindow>();
await Ioc.Default.GetRequiredService<IInfoBarService>().WaitInitializationAsync().ConfigureAwait(false);
break;
}
case LaunchGame:
{
break;
}
}
if (args.Kind == ExtendedActivationKind.Protocol)
{
if (args.TryGetProtocolActivatedUri(out Uri? uri))
{
infoBarService.Information(uri.ToString());
Ioc.Default.GetRequiredService<IInfoBarService>().Information(uri.ToString());
await HandleUrlActivationAsync(uri).ConfigureAwait(false);
}
}

View File

@@ -28,4 +28,22 @@ public static class AppActivationArgumentsExtensions
return false;
}
/// <summary>
/// 尝试获取启动的参数
/// </summary>
/// <param name="activatedEventArgs">应用程序激活参数</param>
/// <param name="arguments">参数</param>
/// <returns>是否存在参数</returns>
public static bool TryGetLaunchActivatedArgument(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
{
arguments = null;
if (activatedEventArgs.Data is ILaunchActivatedEventArgs launchArgs)
{
arguments = launchArgs.Arguments;
return true;
}
return false;
}
}

View File

@@ -70,9 +70,8 @@ public sealed class LogEntryQueue : IDisposable
logDbContext.Database.Migrate();
}
logDbContext.Logs.RemoveRange(logDbContext.Logs);
logDbContext.SaveChanges();
// only raw sql can pass
logDbContext.Database.ExecuteSqlRaw("DELETE FROM logs WHERE Exception IS NULL");
return logDbContext;
}

View File

@@ -41,4 +41,4 @@ public static class ProcessHelper
};
return Process.Start(processInfo);
}
}
}

View File

@@ -10,10 +10,6 @@ namespace Snap.Hutao.Core.Setting;
/// </summary>
internal static class LocalSetting
{
/// <summary>
/// 由于 <see cref="Windows.Foundation.Collections.IPropertySet"/> 没有 nullable context,
/// 在处理引用类型时需要格外小心
/// </summary>
private static readonly ApplicationDataContainer Container;
static LocalSetting()
@@ -21,6 +17,198 @@ internal static class LocalSetting
Container = ApplicationData.Current.LocalSettings;
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static byte Get(string key, byte defaultValue)
{
return Get<byte>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static short Get(string key, short defaultValue)
{
return Get<short>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static ushort Get(string key, ushort defaultValue)
{
return Get<ushort>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static int Get(string key, int defaultValue)
{
return Get<int>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static uint Get(string key, uint defaultValue)
{
return Get<uint>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static ulong Get(string key, ulong defaultValue)
{
return Get<ulong>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static float Get(string key, float defaultValue)
{
return Get<float>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static double Get(string key, double defaultValue)
{
return Get<double>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static bool Get(string key, bool defaultValue)
{
return Get<bool>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static char Get(string key, char defaultValue)
{
return Get<char>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static DateTimeOffset Get(string key, DateTimeOffset defaultValue)
{
return Get<DateTimeOffset>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static TimeSpan Get(string key, TimeSpan defaultValue)
{
return Get<TimeSpan>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static Guid Get(string key, Guid defaultValue)
{
return Get<Guid>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static Windows.Foundation.Point Get(string key, Windows.Foundation.Point defaultValue)
{
return Get<Windows.Foundation.Point>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static Windows.Foundation.Size Get(string key, Windows.Foundation.Size defaultValue)
{
return Get<Windows.Foundation.Size>(key, defaultValue);
}
/// <inheritdoc cref="Get{T}(string, T)"/>
public static Windows.Foundation.Rect Get(string key, Windows.Foundation.Rect defaultValue)
{
return Get<Windows.Foundation.Rect>(key, defaultValue);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, byte value)
{
Set<byte>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, short value)
{
Set<short>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, ushort value)
{
Set<ushort>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, int value)
{
Set<int>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, uint value)
{
Set<uint>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, ulong value)
{
Set<ulong>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, float value)
{
Set<float>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, double value)
{
Set<double>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, bool value)
{
Set<bool>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, char value)
{
Set<char>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, DateTimeOffset value)
{
Set<DateTimeOffset>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, TimeSpan value)
{
Set<TimeSpan>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, Guid value)
{
Set<Guid>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, Windows.Foundation.Point value)
{
Set<Windows.Foundation.Point>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, Windows.Foundation.Size value)
{
Set<Windows.Foundation.Size>(key, value);
}
/// <inheritdoc cref="Set{T}(string, T)"/>
public static void Set(string key, Windows.Foundation.Rect value)
{
Set<Windows.Foundation.Rect>(key, value);
}
/// <summary>
/// 获取设置项的值
/// </summary>
@@ -28,8 +216,8 @@ internal static class LocalSetting
/// <param name="key">键</param>
/// <param name="defaultValue">默认值</param>
/// <returns>获取的值</returns>
[return: MaybeNull]
public static T Get<T>(string key, [AllowNull] T defaultValue = default)
private static T Get<T>(string key, T defaultValue = default)
where T : struct
{
if (Container.Values.TryGetValue(key, out object? value))
{
@@ -49,9 +237,9 @@ internal static class LocalSetting
/// <typeparam name="T">设置项的类型</typeparam>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <returns>设置的值</returns>
public static object? Set<T>(string key, T value)
private static void Set<T>(string key, T value)
where T : struct
{
return Container.Values[key] = value;
Container.Values[key] = value;
}
}

View File

@@ -8,11 +8,6 @@ namespace Snap.Hutao.Core.Setting;
/// </summary>
internal static class SettingKeys
{
/// <summary>
/// 上次打开时App的版本
/// </summary>
public const string LastAppVersion = "LastAppVersion";
/// <summary>
/// 窗体左侧
/// </summary>

View File

@@ -28,4 +28,4 @@ internal class ConcurrentCancellationTokenSource<TItem>
return waitingItems.GetOrAdd(item, new CancellationTokenSource()).Token;
}
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Extension;
namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 信号量扩展
@@ -15,7 +15,7 @@ public static class SemaphoreSlimExtensions
/// <returns>可释放的对象,用于释放信号量</returns>
public static async Task<IDisposable> EnterAsync(this SemaphoreSlim semaphoreSlim)
{
await semaphoreSlim.WaitAsync();
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
return new SemaphoreSlimReleaser(semaphoreSlim);
}

View File

@@ -9,8 +9,9 @@ namespace Snap.Hutao.Core.Threading;
internal static class ThreadHelper
{
/// <summary>
/// 异步切换到主线程
/// 使用此静态方法以 异步切换到 主线程
/// </summary>
/// <remarks>使用 <see cref="Task.Yield"/> 异步切换到 后台线程</remarks>
/// <returns>等待体</returns>
public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
{

View File

@@ -38,18 +38,6 @@ public static class Must
}
}
/// <summary>
/// 任务异常
/// </summary>
/// <param name="message">异常消息</param>
/// <returns>异常的任务</returns>
[SuppressMessage("", "VSTHRD200")]
public static Task Fault(string message)
{
InvalidOperationException exception = new(message);
return Task.FromException(exception);
}
/// <summary>
/// 任务异常
/// </summary>

View File

@@ -44,7 +44,7 @@ internal static class Persistence
/// <param name="appWindow">应用窗体</param>
public static void Save(AppWindow appWindow)
{
LocalSetting.Set(SettingKeys.WindowRect, (ulong)(CompactRect)appWindow.GetRect());
LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)appWindow.GetRect());
}
/// <summary>
@@ -124,7 +124,7 @@ internal static class Persistence
return new(rect.X, rect.Y, rect.Width, rect.Height);
}
public static explicit operator ulong(CompactRect rect)
public static implicit operator ulong(CompactRect rect)
{
return rect.Value;
}

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Extension;
/// <summary>
/// <see cref="BinaryReader"/> 扩展
/// </summary>
public static class BinaryReaderExtensions
public static class BinaryReaderExtension
{
/// <summary>
/// 判断是否处于流的结尾

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Extension;
/// <summary>
/// <see cref="DateTimeOffset"/> 扩展
/// </summary>
public static class DateTimeOffsetExtensions
public static class DateTimeOffsetExtension
{
/// <summary>
/// Converts the current <see cref="DateTimeOffset"/> to a <see cref="DateTimeOffset"/> that represents the local time.

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Extension;
/// <summary>
/// 枚举拓展
/// </summary>
public static class EnumExtensions
public static class EnumExtension
{
/// <summary>
/// 获取枚举的描述

View File

@@ -9,7 +9,7 @@ namespace Snap.Hutao.Extension;
/// <summary>
/// <see cref="IEnumerable{T}"/> 扩展
/// </summary>
public static partial class EnumerableExtensions
public static partial class EnumerableExtension
{
/// <inheritdoc cref="Enumerable.Average(IEnumerable{int})"/>
public static double AverageNoThrow(this List<int> source)

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Extension;
/// <summary>
/// 数高性能扩展
/// </summary>
public static class NumberExtensions
public static class NumberExtension
{
/// <summary>
/// 获取从右向左某位上的数字

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Extension;
/// <summary>
/// 对象扩展
/// </summary>
public static class ObjectExtensions
public static class ObjectExtension
{
/// <summary>
/// <see langword="as"/> 的链式调用扩展

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Extension;
/// <summary>
/// 包版本扩展
/// </summary>
public static class PackageVersionExtensions
public static class PackageVersionExtension
{
/// <summary>
/// 将包版本转换为版本

View File

@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.Input;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Service.AppCenter;
namespace Snap.Hutao.Factory;
@@ -11,15 +12,18 @@ namespace Snap.Hutao.Factory;
[Injection(InjectAs.Transient, typeof(IAsyncRelayCommandFactory))]
internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
{
private readonly ILogger logger;
private readonly ILogger<AsyncRelayCommandFactory> logger;
private readonly AppCenter appCenter;
/// <summary>
/// 构造一个新的异步命令工厂
/// </summary>
/// <param name="logger">日志器</param>
public AsyncRelayCommandFactory(ILogger<AsyncRelayCommandFactory> logger)
/// <param name="appCenter">App Center</param>
public AsyncRelayCommandFactory(ILogger<AsyncRelayCommandFactory> logger, AppCenter appCenter)
{
this.logger = logger;
this.appCenter = appCenter;
}
/// <inheritdoc/>
@@ -94,6 +98,7 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
{
Exception baseException = exception.GetBaseException();
logger.LogError(EventIds.AsyncCommandException, baseException, "{name} Exception", nameof(AsyncRelayCommand));
appCenter.TrackError(exception);
}
}
}

View File

@@ -0,0 +1,12 @@
<Window
x:Class="Snap.Hutao.LaunchGameWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Snap.Hutao"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao;
/// <summary>
/// 启动游戏窗口
/// </summary>
public sealed partial class LaunchGameWindow : Window
{
/// <summary>
/// 构造一个新的启动游戏窗口
/// </summary>
public LaunchGameWindow()
{
InitializeComponent();
}
}

View File

@@ -8,16 +8,7 @@ namespace Snap.Hutao.Message;
/// <summary>
/// 成就存档切换消息
/// </summary>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
internal class AchievementArchiveChangedMessage : ValueChangedMessage<AchievementArchive>
{
/// <summary>
/// 构造一个新的用户切换消息
/// </summary>
/// <param name="oldArchive">老用户</param>
/// <param name="newArchive">新用户</param>
public AchievementArchiveChangedMessage(AchievementArchive? oldArchive, AchievementArchive? newArchive)
: base(oldArchive, newArchive)
{
}
}

View File

@@ -8,16 +8,7 @@ namespace Snap.Hutao.Message;
/// <summary>
/// 祈愿记录存档切换消息
/// </summary>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
internal class GachaArchiveChangedMessage : ValueChangedMessage<GachaArchive>
{
/// <summary>
/// 构造一个新的用户切换消息
/// </summary>
/// <param name="oldArchive">老用户</param>
/// <param name="newArchive">新用户</param>
public GachaArchiveChangedMessage(GachaArchive? oldArchive, GachaArchive? newArchive)
: base(oldArchive, newArchive)
{
}
}

View File

@@ -8,15 +8,7 @@ namespace Snap.Hutao.Message;
/// <summary>
/// 用户切换消息
/// </summary>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
internal class UserChangedMessage : ValueChangedMessage<User>
{
/// <summary>
/// 构造一个新的用户切换消息
/// </summary>
/// <param name="oldUser">老用户</param>
/// <param name="newUser">新用户</param>
public UserChangedMessage(User? oldUser, User? newUser)
: base(oldUser, newUser)
{
}
}

View File

@@ -7,9 +7,17 @@ namespace Snap.Hutao.Message;
/// 值变化消息
/// </summary>
/// <typeparam name="TValue">值的类型</typeparam>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
internal abstract class ValueChangedMessage<TValue>
where TValue : class
{
/// <summary>
/// 动态访问
/// </summary>
public ValueChangedMessage()
{
}
/// <summary>
/// 构造一个新的值变化消息
/// </summary>
@@ -24,10 +32,10 @@ internal abstract class ValueChangedMessage<TValue>
/// <summary>
/// 旧的值
/// </summary>
public TValue? OldValue { get; private set; }
public TValue? OldValue { get; set; }
/// <summary>
/// 新的值
/// </summary>
public TValue? NewValue { get; private set; }
public TValue? NewValue { get; set; }
}

View File

@@ -1,12 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
namespace Snap.Hutao.Model.Binding;
/// <summary>
/// 用于视图绑定的成就
/// </summary>
public class Achievement : Observable
public class Achievement : ObservableObject
{
/// <summary>
/// 满进度占位符
@@ -28,7 +30,7 @@ public class Achievement : Observable
this.inner = inner;
this.entity = entity;
// Property should only be set when is user checking.
// Property should only be set when it's user checking.
isChecked = (int)entity.Status >= 2;
}
@@ -50,7 +52,7 @@ public class Achievement : Observable
get => isChecked;
set
{
Set(ref isChecked, value);
SetProperty(ref isChecked, value);
// Only update state when checked
if (value)
@@ -67,6 +69,6 @@ public class Achievement : Observable
/// </summary>
public string Time
{
get => entity.Time.ToString("yyyy-MM-dd HH:mm:ss");
get => entity.Time.ToString("yyyy.MM.dd HH:mm:ss");
}
}

View File

@@ -11,8 +11,19 @@ public class Reliquary : EquipBase
/// <summary>
/// 副属性列表
/// </summary>
[Obsolete]
public List<ReliquarySubProperty> SubProperties { get; set; } = default!;
/// <summary>
/// 初始词条
/// </summary>
public List<ReliquarySubProperty> PrimarySubProperties { get; set; } = default!;
/// <summary>
/// 强化词条
/// </summary>
public List<ReliquarySubProperty> SecondarySubProperties { get; set; } = default!;
/// <summary>
/// 评分
/// </summary>

View File

@@ -24,7 +24,7 @@ public class GachaStatistics
public TypedWishSummary PermanentWish { get; set; } = default!;
/// <summary>
/// 历史
/// 历史卡池
/// </summary>
public List<HistoryWish> HistoryWishes { get; set; } = default!;

View File

@@ -10,6 +10,16 @@ namespace Snap.Hutao.Model.Binding.Gacha;
/// </summary>
public class HistoryWish : WishBase
{
/// <summary>
/// 版本
/// </summary>
public string Version { get; set; } = default!;
/// <summary>
/// 卡池图片
/// </summary>
public Uri BannerImage { get; set; } = default!;
/// <summary>
/// 五星Up
/// </summary>

View File

@@ -0,0 +1,47 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 角色
/// </summary>
public class ComplexAvatar
{
/// <summary>
/// 构造一个胡桃数据库角色
/// </summary>
/// <param name="avatar">元数据角色</param>
/// <param name="rate">率</param>
public ComplexAvatar(Avatar avatar, double rate)
{
Name = avatar.Name;
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
Quality = avatar.Quality;
Rate = $"{rate:P3}";
}
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public Uri Icon { get; set; } = default!;
/// <summary>
/// 星级
/// </summary>
public ItemQuality Quality { get; set; }
/// <summary>
/// 比率
/// </summary>
public string Rate { get; set; } = default!;
}

View File

@@ -0,0 +1,43 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 角色搭配
/// </summary>
public class ComplexAvatarCollocation : ComplexAvatar
{
/// <summary>
/// 构造一个新的角色搭配
/// </summary>
/// <param name="avatar">角色</param>
/// <param name="rate">比率</param>
public ComplexAvatarCollocation(Avatar avatar)
: base(avatar, 0)
{
}
/// <summary>
/// 角色Id
/// </summary>
public AvatarId AvatarId { get; set; }
/// <summary>
/// 角色
/// </summary>
public List<ComplexAvatar> Avatars { get; set; } = default!;
/// <summary>
/// 武器
/// </summary>
public List<ComplexWeapon> Weapons { get; set; } = default!;
/// <summary>
/// 圣遗物套装
/// </summary>
public List<ComplexReliquarySet> ReliquarySets { get; set; } = default!;
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Avatar;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 角色命座信息
/// </summary>
internal class ComplexAvatarConstellationInfo : ComplexAvatar
{
/// <summary>
/// 构造一个新的角色命座信息
/// </summary>
/// <param name="avatar">角色</param>
/// <param name="rate">持有率</param>
/// <param name="rates">命座比率</param>
public ComplexAvatarConstellationInfo(Avatar avatar, double rate, IEnumerable<double> rates)
: base(avatar, rate)
{
Rates = rates.Select(r => $"{r:P3}").ToList();
}
/// <summary>
/// 命座比率
/// </summary>
public List<string> Rates { get; set; }
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 角色榜
/// </summary>
internal class ComplexAvatarRank
{
/// <summary>
/// 层数
/// </summary>
public string Floor { get; set; } = default!;
/// <summary>
/// 排行信息
/// </summary>
public List<ComplexAvatar> Avatars { get; set; } = default!;
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web.Hutao.Model;
using System.Text;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 圣遗物套装
/// </summary>
public class ComplexReliquarySet
{
/// <summary>
/// 构造一个新的胡桃数据库圣遗物套装
/// </summary>
/// <param name="reliquarySetRate">圣遗物套装率</param>
/// <param name="idReliquarySetMap">圣遗物套装映射</param>
public ComplexReliquarySet(ItemRate<ReliquarySets, double> reliquarySetRate, Dictionary<int, Metadata.Reliquary.ReliquarySet> idReliquarySetMap)
{
ReliquarySets sets = reliquarySetRate.Item;
if (sets.Count >= 1)
{
StringBuilder setStringBuilder = new();
List<Uri> icons = new();
foreach (ReliquarySet set in sets)
{
Metadata.Reliquary.ReliquarySet metaSet = idReliquarySetMap[set.EquipAffixId / 10];
if (setStringBuilder.Length != 0)
{
setStringBuilder.Append(Environment.NewLine);
}
setStringBuilder.Append(set.Count).Append('×').Append(metaSet.Name);
icons.Add(RelicIconConverter.IconNameToUri(metaSet.Icon));
}
Name = setStringBuilder.ToString();
Icons = icons;
}
else
{
Name = "无圣遗物";
}
Rate = $"{reliquarySetRate.Rate:P3}";
}
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public List<Uri> Icons { get; set; } = default!;
/// <summary>
/// 比率
/// </summary>
public string Rate { get; set; } = default!;
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Web.Hutao.Model;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 队伍排行
/// </summary>
internal class ComplexTeamRank
{
/// <summary>
/// 构造一个新的队伍排行
/// </summary>
/// <param name="teamRank">队伍排行</param>
/// <param name="idAvatarMap">映射</param>
public ComplexTeamRank(TeamAppearance teamRank, Dictionary<int, Avatar> idAvatarMap)
{
Floor = $"第 {teamRank.Floor} 层";
Up = teamRank.Up.Select(teamRate => new Team(teamRate, idAvatarMap)).ToList();
Down = teamRank.Down.Select(teamRate => new Team(teamRate, idAvatarMap)).ToList();
}
/// <summary>
/// 层数
/// </summary>
public string Floor { get; set; } = default!;
/// <summary>
/// 上半阵容
/// </summary>
public List<Team> Up { get; set; } = default!;
/// <summary>
/// 下半阵容
/// </summary>
public List<Team> Down { get; set; } = default!;
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Metadata.Weapon;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 胡桃数据库武器
/// </summary>
public class ComplexWeapon
{
/// <summary>
/// 构造一个胡桃数据库武器
/// </summary>
/// <param name="weapon">元数据武器</param>
/// <param name="rate">率</param>
public ComplexWeapon(Weapon weapon, double rate)
{
Name = weapon.Name;
Icon = EquipIconConverter.IconNameToUri(weapon.Icon);
Quality = weapon.Quality;
Rate = $"{rate:P3}";
}
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public Uri Icon { get; set; } = default!;
/// <summary>
/// 星级
/// </summary>
public ItemQuality Quality { get; set; }
/// <summary>
/// 比率
/// </summary>
public string Rate { get; set; } = default!;
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Web.Hutao.Model;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 队伍
/// </summary>
internal class Team : List<ComplexAvatar>
{
/// <summary>
/// 构造一个新的队伍
/// </summary>
/// <param name="team">队伍</param>
/// <param name="idAvatarMap">映射</param>
public Team(ItemRate<string, int> team, Dictionary<int, Avatar> idAvatarMap)
: base(4)
{
IEnumerable<int> ids = team.Item.Split(',').Select(i => int.Parse(i));
foreach (int id in ids)
{
Add(new(idAvatarMap[id], 0));
}
Rate = $"上场 {team.Rate} 次";
}
/// <summary>
/// 上场次数
/// </summary>
public string Rate { get; set; }
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Binding.LaunchGame;
/// <summary>
/// 服务器方案
/// </summary>
/// <summary>
/// 启动方案
/// </summary>
public class LaunchScheme
{
/// <summary>
/// 构造一个新的启动方案
/// </summary>
/// <param name="name">名称</param>
/// <param name="channel">通道</param>
/// <param name="cps">通道描述字符串</param>
/// <param name="subChannel">子通道</param>
public LaunchScheme(string name, string channel, string subChannel)
{
Name = name;
Channel = channel;
SubChannel = subChannel;
}
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 通道
/// </summary>
public string Channel { get; set; }
/// <summary>
/// 子通道
/// </summary>
public string SubChannel { get; set; }
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Binding.LaunchGame;
/// <summary>
/// 启动类型
/// </summary>
public enum SchemeType
{
/// <summary>
/// 国际服
/// </summary>
Mihoyo,
/// <summary>
/// 国服官服
/// </summary>
Officical,
/// <summary>
/// 渠道服
/// </summary>
Bilibili,
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Extension;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Bbs.User;
@@ -12,7 +13,7 @@ namespace Snap.Hutao.Model.Binding;
/// <summary>
/// 用于视图绑定的用户
/// </summary>
public class User : Observable
public class User : ObservableObject
{
private readonly EntityUser inner;
@@ -44,7 +45,7 @@ public class User : Observable
public UserGameRole? SelectedUserGameRole
{
get => selectedUserGameRole;
private set => Set(ref selectedUserGameRole, value);
private set => SetProperty(ref selectedUserGameRole, value);
}
/// <inheritdoc cref="EntityUser.IsSelected"/>
@@ -58,7 +59,19 @@ public class User : Observable
public Cookie Cookie
{
get => inner.Cookie;
set => inner.Cookie = value;
set
{
inner.Cookie = value;
OnPropertyChanged(nameof(HasSToken));
}
}
/// <summary>
/// 是否拥有 SToken
/// </summary>
public bool HasSToken
{
get => inner.Cookie.ContainsSToken();
}
/// <summary>
@@ -71,6 +84,17 @@ public class User : Observable
/// </summary>
public bool IsInitialized { get => isInitialized; }
/// <summary>
/// 更新SToken
/// </summary>
/// <param name="uid">uid</param>
/// <param name="cookie">cookie</param>
internal void UpdateSToken(string uid, Cookie cookie)
{
Cookie.InsertSToken(uid, cookie);
OnPropertyChanged(nameof(HasSToken));
}
/// <summary>
/// 从数据库恢复用户
/// </summary>
@@ -79,11 +103,7 @@ public class User : Observable
/// <param name="userGameRoleClient">角色客户端</param>
/// <param name="token">取消令牌</param>
/// <returns>用户是否初始化完成若Cookie失效会返回 <see langword="false"/> </returns>
internal static async Task<User?> ResumeAsync(
EntityUser inner,
UserClient userClient,
BindingClient userGameRoleClient,
CancellationToken token = default)
internal static async Task<User?> ResumeAsync(EntityUser inner, UserClient userClient, BindingClient userGameRoleClient, CancellationToken token = default)
{
User user = new(inner);
bool successful = await user.InitializeCoreAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
@@ -98,36 +118,20 @@ public class User : Observable
/// <param name="userGameRoleClient">角色客户端</param>
/// <param name="token">取消令牌</param>
/// <returns>用户是否初始化完成若Cookie失效会返回 <see langword="null"/> </returns>
internal static async Task<User?> CreateAsync(
Cookie cookie,
UserClient userClient,
BindingClient userGameRoleClient,
CancellationToken token = default)
internal static async Task<User?> CreateAsync(Cookie cookie, UserClient userClient, BindingClient userGameRoleClient, CancellationToken token = default)
{
User user = new(EntityUser.Create(cookie));
bool successful = await user.InitializeCoreAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
return successful ? user : null;
}
private async Task<bool> InitializeCoreAsync(
UserClient userClient,
BindingClient userGameRoleClient,
CancellationToken token = default)
private async Task<bool> InitializeCoreAsync(UserClient userClient, BindingClient userGameRoleClient, CancellationToken token = default)
{
if (isInitialized)
{
return true;
}
await InitializeUserInfoAndUserGameRolesAsync(userClient, userGameRoleClient, token).ConfigureAwait(false);
isInitialized = true;
return UserInfo != null && UserGameRoles.Any();
}
private async Task InitializeUserInfoAndUserGameRolesAsync(UserClient userClient, BindingClient userGameRoleClient, CancellationToken token)
{
UserInfo = await userClient
.GetUserFullInfoAsync(this, token)
.ConfigureAwait(false);
@@ -137,5 +141,9 @@ public class User : Observable
.ConfigureAwait(false);
SelectedUserGameRole = UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
isInitialized = true;
return UserInfo != null && UserGameRoles.Any();
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Binding.LaunchGame;
using Snap.Hutao.Web.Hoyolab;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Snap.Hutao.Model.Entity;
/// <summary>
/// 游戏内账号
/// </summary>
[Table("game_accounts")]
public class GameAccount : ISelectable
{
/// <summary>
/// 内部Id
/// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid InnerId { get; set; }
/// <inheritdoc/>
public bool IsSelected { get; set; }
/// <summary>
/// 对应的Uid
/// </summary>
public string? AttachUid { get; set; }
/// <summary>
/// 服务器类型
/// </summary>
public SchemeType Type { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// MIHOYOSDK_ADL_PROD_CN_h3123967166
/// </summary>
public string MihoyoSDK { get; set; } = default!;
}

View File

@@ -12,6 +12,16 @@ namespace Snap.Hutao.Model.Entity;
[Table("settings")]
public class SettingEntry
{
/// <summary>
/// 游戏路径
/// </summary>
public const string GamePath = "GamePath";
/// <summary>
/// 空的历史记录卡池是否可见
/// </summary>
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
/// <summary>
/// 构造一个新的设置入口
/// </summary>

View File

@@ -40,4 +40,4 @@ public class User : ISelectable
{
return new() { Cookie = cookie };
}
}
}

View File

@@ -28,7 +28,7 @@ public class UIAFInfo
public DateTimeOffset ExportDateTime
{
// Hot fix | 1.0.31 | UIAF.Info.ExportTimestamp can be milliseconds
get => DateTimeOffsetExtensions.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
}
/// <summary>

View File

@@ -27,14 +27,16 @@ public class UIGFInfo
/// 导出的时间戳
/// </summary>
[JsonPropertyName("export_timestamp")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public long? ExportTimestamp { get; set; }
/// <summary>
/// 导出时间
/// </summary>
[JsonIgnore]
public DateTimeOffset ExportDateTime
{
get => DateTimeOffsetExtensions.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
}
/// <summary>

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;
@@ -84,6 +85,11 @@ public class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality
/// </summary>
public IEnumerable<Costume> Costumes { get; set; } = default!;
/// <summary>
/// [非元数据] 搭配数据
/// </summary>
public ComplexAvatarCollocation? Collocation { get; set; }
/// <summary>
/// 转换为基础物品
/// </summary>

View File

@@ -1,67 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 角色ID
/// </summary>
[SuppressMessage("", "SA1600")]
public static class AvatarIds
{
public const int Ayaka = 10000002;
public const int Qin = 10000003;
public const int Lisa = 10000006;
public const int Barbara = 10000014;
public const int Kaeya = 10000015;
public const int Diluc = 10000016;
public const int Razor = 10000020;
public const int Ambor = 10000021;
public const int Venti = 10000022;
public const int Xiangling = 10000023;
public const int Beidou = 10000024;
public const int Xingqiu = 10000025;
public const int Xiao = 10000026;
public const int Ningguang = 10000027;
public const int Klee = 10000029;
public const int Zhongli = 10000030;
public const int Fischl = 10000031;
public const int Bennett = 10000032;
public const int Tartaglia = 10000033;
public const int Noel = 10000034;
public const int Qiqi = 10000035;
public const int Chongyun = 10000036;
public const int Ganyu = 10000037;
public const int Albedo = 10000038;
public const int Diona = 10000039;
public const int Mona = 10000041;
public const int Keqing = 10000042;
public const int Sucrose = 10000043;
public const int Xinyan = 10000044;
public const int Rosaria = 10000045;
public const int Hutao = 10000046;
public const int Kazuha = 10000047;
public const int Feiyan = 10000048;
public const int Yoimiya = 10000049;
public const int Tohma = 10000050;
public const int Eula = 10000051;
public const int Shougun = 10000052;
public const int Sayu = 10000053;
public const int Kokomi = 10000054;
public const int Gorou = 10000055;
public const int Sara = 10000056;
public const int Itto = 10000057;
public const int Yae = 10000058;
public const int Heizou = 10000059;
public const int Yelan = 10000060;
public const int Aloy = 10000062;
public const int Shenhe = 10000063;
public const int Yunjin = 10000064;
public const int Shinobu = 10000065;
public const int Ayato = 10000066;
public const int Collei = 10000067;
public const int Dori = 10000068;
public const int Tighnari = 10000069;
public const int Cyno = 10000071;
public const int Candace = 10000072;
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 料理奖励
/// </summary>
public class CookBonus
{
/// <summary>
/// 原型名称
/// </summary>
public string OriginName { get; set; } = default!;
/// <summary>
/// 原型描述
/// </summary>
public string OriginDescription { get; set; } = default!;
/// <summary>
/// 原型图标
/// </summary>
public string OriginIcon { get; set; } = default!;
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 效果描述
/// </summary>
public string EffectDescription { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = default!;
/// <summary>
/// 物品等级
/// </summary>
public ItemQuality RankLevel { get; set; }
/// <summary>
/// 材料列表
/// </summary>
public List<ItemWithCount> InputList { get; set; } = default!;
}

View File

@@ -76,6 +76,11 @@ public class FetterInfo
/// </summary>
public string CvKorean { get; set; } = default!;
/// <summary>
/// 料理
/// </summary>
public CookBonus? CookBonus { get; set; }
/// <summary>
/// 好感语音
/// </summary>

View File

@@ -0,0 +1,37 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 带有个数的物品
/// </summary>
public class ItemWithCount
{
/// <summary>
/// 物品Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = default!;
/// <summary>
/// 物品等级
/// </summary>
public ItemQuality RankLevel { get; set; }
/// <summary>
/// 数量
/// </summary>
public int Count { get; set; }
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata;
/// <summary>
/// 角色ID
/// </summary>
[SuppressMessage("", "SA1600")]
public static class AvatarIds
{
public static readonly AvatarId Ayaka = 10000002;
public static readonly AvatarId Qin = 10000003;
public static readonly AvatarId Lisa = 10000006;
public static readonly AvatarId Barbara = 10000014;
public static readonly AvatarId Kaeya = 10000015;
public static readonly AvatarId Diluc = 10000016;
public static readonly AvatarId Razor = 10000020;
public static readonly AvatarId Ambor = 10000021;
public static readonly AvatarId Venti = 10000022;
public static readonly AvatarId Xiangling = 10000023;
public static readonly AvatarId Beidou = 10000024;
public static readonly AvatarId Xingqiu = 10000025;
public static readonly AvatarId Xiao = 10000026;
public static readonly AvatarId Ningguang = 10000027;
public static readonly AvatarId Klee = 10000029;
public static readonly AvatarId Zhongli = 10000030;
public static readonly AvatarId Fischl = 10000031;
public static readonly AvatarId Bennett = 10000032;
public static readonly AvatarId Tartaglia = 10000033;
public static readonly AvatarId Noel = 10000034;
public static readonly AvatarId Qiqi = 10000035;
public static readonly AvatarId Chongyun = 10000036;
public static readonly AvatarId Ganyu = 10000037;
public static readonly AvatarId Albedo = 10000038;
public static readonly AvatarId Diona = 10000039;
public static readonly AvatarId Mona = 10000041;
public static readonly AvatarId Keqing = 10000042;
public static readonly AvatarId Sucrose = 10000043;
public static readonly AvatarId Xinyan = 10000044;
public static readonly AvatarId Rosaria = 10000045;
public static readonly AvatarId Hutao = 10000046;
public static readonly AvatarId Kazuha = 10000047;
public static readonly AvatarId Feiyan = 10000048;
public static readonly AvatarId Yoimiya = 10000049;
public static readonly AvatarId Tohma = 10000050;
public static readonly AvatarId Eula = 10000051;
public static readonly AvatarId Shougun = 10000052;
public static readonly AvatarId Sayu = 10000053;
public static readonly AvatarId Kokomi = 10000054;
public static readonly AvatarId Gorou = 10000055;
public static readonly AvatarId Sara = 10000056;
public static readonly AvatarId Itto = 10000057;
public static readonly AvatarId Yae = 10000058;
public static readonly AvatarId Heizou = 10000059;
public static readonly AvatarId Yelan = 10000060;
public static readonly AvatarId Aloy = 10000062;
public static readonly AvatarId Shenhe = 10000063;
public static readonly AvatarId Yunjin = 10000064;
public static readonly AvatarId Shinobu = 10000065;
public static readonly AvatarId Ayato = 10000066;
public static readonly AvatarId Collei = 10000067;
public static readonly AvatarId Dori = 10000068;
public static readonly AvatarId Tighnari = 10000069;
public static readonly AvatarId Nilou = 10000070;
public static readonly AvatarId Cyno = 10000071;
public static readonly AvatarId Candace = 10000072;
public static readonly AvatarId Nahida = 10000073;
public static readonly AvatarId Layla = 10000074;
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Control;
namespace Snap.Hutao.Model.Metadata.Converter;
/// <summary>
/// 立绘图标转换器
/// </summary>
internal class GachaAvatarIconConverter : ValueConverterBase<string, Uri>
{
private const string BaseUrl = "https://static.snapgenshin.com/GachaAvatarIcon/UI_Gacha_AvatarIcon_{0}.png";
/// <summary>
/// 名称转Uri
/// </summary>
/// <param name="name">名称</param>
/// <returns>链接</returns>
public static Uri IconNameToUri(string name)
{
name = name["UI_AvatarIcon_".Length..];
return new Uri(string.Format(BaseUrl, name));
}
/// <inheritdoc/>
public override Uri Convert(string from)
{
return IconNameToUri(from);
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Control;
namespace Snap.Hutao.Model.Metadata.Converter;
/// <summary>
/// 立绘转换器
/// </summary>
internal class GachaAvatarImgConverter : ValueConverterBase<string, Uri>
{
private const string BaseUrl = "https://static.snapgenshin.com/GachaAvatarImg/UI_Gacha_AvatarImg_{0}.png";
/// <summary>
/// 名称转Uri
/// </summary>
/// <param name="name">名称</param>
/// <returns>链接</returns>
public static Uri IconNameToUri(string name)
{
name = name["UI_AvatarIcon_".Length..];
return new Uri(string.Format(BaseUrl, name));
}
/// <inheritdoc/>
public override Uri Convert(string from)
{
return IconNameToUri(from);
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Control;
namespace Snap.Hutao.Model.Metadata.Converter;
/// <summary>
/// 物品图片转换器
/// </summary>
internal class ItemIconConverter : ValueConverterBase<string, Uri>
{
private const string BaseUrl = "https://static.snapgenshin.com/ItemIcon/{0}.png";
/// <summary>
/// 名称转Uri
/// </summary>
/// <param name="name">名称</param>
/// <returns>链接</returns>
public static Uri IconNameToUri(string name)
{
return new Uri(string.Format(BaseUrl, name));
}
/// <inheritdoc/>
public override Uri Convert(string from)
{
return IconNameToUri(from);
}
}

View File

@@ -13,6 +13,8 @@ internal class SkillIconConverter : ValueConverterBase<string, Uri>
private const string SkillUrl = "https://static.snapgenshin.com/Skill/{0}.png";
private const string TalentUrl = "https://static.snapgenshin.com/Talent/{0}.png";
private static readonly Uri UIIconNone = new("https://static.snapgenshin.com/Bg/UI_Icon_None.png");
/// <summary>
/// 名称转Uri
/// </summary>
@@ -20,6 +22,11 @@ internal class SkillIconConverter : ValueConverterBase<string, Uri>
/// <returns>链接</returns>
public static Uri IconNameToUri(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return UIIconNone;
}
if (name.StartsWith("UI_Talent_"))
{
return new Uri(string.Format(TalentUrl, name));

View File

@@ -15,6 +15,16 @@ public class GachaEvent
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 版本
/// </summary>
public string Version { get; set; } = default!;
/// <summary>
/// 卡池图
/// </summary>
public Uri Banner { get; set; } = default!;
/// <summary>
/// 开始时间
/// </summary>

View File

@@ -11,15 +11,30 @@ public class ReliquarySet
/// <summary>
/// 套装Id
/// </summary>
public int SetId { get; set; } = default!;
public int SetId { get; set; }
/// <summary>
/// 装备被动Id
/// </summary>
public int EquipAffixId { get; set; }
/// <summary>
/// 套装名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 套装图标
/// </summary>
public string Icon { get; set; } = default!;
/// <summary>
/// 需要的数量
/// </summary>
public IEnumerable<int> NeedNumber { get; set; } = default!;
public List<int> NeedNumber { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public IEnumerable<string> Descriptions { get; set; } = default!;
public List<string> Descriptions { get; set; } = default!;
}

View File

@@ -61,7 +61,10 @@ public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality
/// <inheritdoc/>
[JsonIgnore]
public ItemQuality Quality => RankLevel;
public ItemQuality Quality
{
get => RankLevel;
}
/// <summary>
/// 转换为基础物品

View File

@@ -0,0 +1,35 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
namespace Snap.Hutao.Model;
/// <summary>
/// 封装带有名称描述的值
/// 在绑定枚举变量时非常有用
/// </summary>
/// <typeparam name="T">包含值的类型</typeparam>
public class NamedValue<T>
{
/// <summary>
/// 构造一个新的命名的值
/// </summary>
/// <param name="name">命名</param>
/// <param name="value">值</param>
public NamedValue(string name, T value)
{
Name = name;
Value = value;
}
/// <summary>
/// 名称
/// </summary>
public string Name { get; }
/// <summary>
/// 值
/// </summary>
public T Value { get; }
}

View File

@@ -1,44 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Model;
/// <summary>
/// 简单的实现了 <see cref="INotifyPropertyChanged"/> 接口
/// </summary>
public abstract class Observable : INotifyPropertyChanged
{
/// <inheritdoc/>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// 设置字段的值
/// </summary>
/// <typeparam name="T">字段类型</typeparam>
/// <param name="storage">现有值</param>
/// <param name="value">新的值</param>
/// <param name="propertyName">属性名称</param>
/// <returns>项是否更新</returns>
protected bool Set<T>([NotNullIfNotNull("value")] ref T storage, T value, [CallerMemberName] string propertyName = default!)
{
if (Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// 触发 <see cref="PropertyChanged"/>
/// </summary>
/// <param name="propertyName">属性名称</param>
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive.Converter;
namespace Snap.Hutao.Model.Primitive;
/// <summary>
/// 角色Id
/// </summary>
[JsonConverter(typeof(AvatarIdConverter))]
public readonly struct AvatarId : IEquatable<AvatarId>
{
/// <summary>
/// 值
/// </summary>
public readonly int Value;
/// <summary>
/// Initializes a new instance of the <see cref="AvatarId"/> struct.
/// </summary>
/// <param name="value">value</param>
public AvatarId(int value)
{
Value = value;
}
public static implicit operator int(AvatarId value)
{
return value.Value;
}
public static implicit operator AvatarId(int value)
{
return new(value);
}
public static bool operator ==(AvatarId left, AvatarId right)
{
return left.Value == right.Value;
}
public static bool operator !=(AvatarId left, AvatarId right)
{
return !(left == right);
}
/// <inheritdoc/>
public bool Equals(AvatarId other)
{
return Value == other.Value;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is AvatarId other && Equals(other);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return Value.GetHashCode();
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Primitive.Converter;
/// <summary>
/// 角色Id转换器
/// </summary>
internal class AvatarIdConverter : JsonConverter<AvatarId>
{
/// <inheritdoc/>
public override AvatarId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.GetInt32();
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, AvatarId value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value);
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Primitive.Converter;
/// <summary>
/// 武器Id转换器
/// </summary>
internal class WeaponIdConverter : JsonConverter<WeaponId>
{
/// <inheritdoc/>
public override WeaponId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.GetInt32();
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, WeaponId value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value);
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive.Converter;
namespace Snap.Hutao.Model.Primitive;
/// <summary>
/// 武器Id
/// </summary>
[JsonConverter(typeof(WeaponIdConverter))]
public readonly struct WeaponId : IEquatable<WeaponId>
{
/// <summary>
/// 值
/// </summary>
public readonly int Value;
/// <summary>
/// Initializes a new instance of the <see cref="WeaponId"/> struct.
/// </summary>
/// <param name="value">value</param>
public WeaponId(int value)
{
Value = value;
}
public static implicit operator int(WeaponId value)
{
return value.Value;
}
public static implicit operator WeaponId(int value)
{
return new(value);
}
public static bool operator ==(WeaponId left, WeaponId right)
{
return left.Value == right.Value;
}
public static bool operator !=(WeaponId left, WeaponId right)
{
return !(left == right);
}
/// <inheritdoc/>
public bool Equals(WeaponId other)
{
return Value == other.Value;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is WeaponId other && Equals(other);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return Value.GetHashCode();
}
}

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
namespace Snap.Hutao.Model;
/// <summary>
@@ -8,7 +10,7 @@ namespace Snap.Hutao.Model;
/// 默认为选中状态
/// </summary>
/// <typeparam name="T">值的类型</typeparam>
public class Selectable<T> : Observable
public class Selectable<T> : ObservableObject
where T : class
{
private readonly Action? selectedChanged;
@@ -35,7 +37,7 @@ public class Selectable<T> : Observable
get => isSelected;
set
{
Set(ref isSelected, value);
SetProperty(ref isSelected, value);
selectedChanged?.Invoke();
}
}
@@ -43,5 +45,5 @@ public class Selectable<T> : Observable
/// <summary>
/// 存放的对象
/// </summary>
public T Value { get => value; set => Set(ref this.value, value); }
}
public T Value { get => value; set => SetProperty(ref this.value, value); }
}

View File

@@ -9,7 +9,7 @@
<Identity
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
Publisher="CN=DGP Studio"
Version="1.1.9.0" />
Version="1.1.14.0" />
<Properties>
<DisplayName>胡桃</DisplayName>

View File

@@ -7,7 +7,6 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Logging;
using System.Runtime.InteropServices;
using Windows.UI.ViewManagement;
using WinRT;
namespace Snap.Hutao;
@@ -20,6 +19,7 @@ public static partial class Program
/// <summary>
/// 主线程队列
/// </summary>
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[SuppressMessage("", "SA1401")]
internal static volatile DispatcherQueue? DispatcherQueue;
@@ -31,6 +31,7 @@ public static partial class Program
private static void Main(string[] args)
{
_ = args;
XamlCheckProcessRequirements();
ComWrappersSupport.InitializeComWrappers();
@@ -74,7 +75,6 @@ public static partial class Program
// Discrete services
.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
.AddSingleton(new UISettings())
.BuildServiceProvider();

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.InterChange.Achievement;
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
@@ -63,7 +64,7 @@ public class AchievementDbOperation
if (entity == null && uiaf != null)
{
AddEntity(EntityAchievement.Create(archiveId, uiaf));
appDbContext.Achievements.AddAndSave(EntityAchievement.Create(archiveId, uiaf));
add++;
continue;
}
@@ -85,8 +86,8 @@ public class AchievementDbOperation
if (aggressive)
{
RemoveEntity(entity);
AddEntity(EntityAchievement.Create(archiveId, uiaf));
appDbContext.Achievements.RemoveAndSave(entity);
appDbContext.Achievements.AddAndSave(EntityAchievement.Create(archiveId, uiaf));
update++;
}
}
@@ -96,7 +97,7 @@ public class AchievementDbOperation
moveEntity = false;
moveUIAF = true;
AddEntity(EntityAchievement.Create(archiveId, uiaf));
appDbContext.Achievements.AddAndSave(EntityAchievement.Create(archiveId, uiaf));
add++;
}
}
@@ -115,7 +116,7 @@ public class AchievementDbOperation
/// <returns>导入结果</returns>
public ImportResult Overwrite(Guid archiveId, IEnumerable<EntityAchievement> items)
{
IQueryable<EntityAchievement> oldData = appDbContext.Achievements
IOrderedQueryable<EntityAchievement> oldData = appDbContext.Achievements
.Where(a => a.ArchiveId == archiveId)
.OrderBy(a => a.Id);
@@ -142,13 +143,13 @@ public class AchievementDbOperation
if (oldEntity == null && newEntity != null)
{
AddEntity(newEntity);
appDbContext.Achievements.AddAndSave(newEntity);
add++;
continue;
}
else if (oldEntity != null && newEntity == null)
{
RemoveEntity(oldEntity);
appDbContext.Achievements.RemoveAndSave(oldEntity);
remove++;
continue;
}
@@ -157,7 +158,7 @@ public class AchievementDbOperation
{
moveOld = true;
moveNew = false;
RemoveEntity(oldEntity);
appDbContext.Achievements.RemoveAndSave(oldEntity);
remove++;
}
else if (oldEntity.Id == newEntity.Id)
@@ -172,8 +173,8 @@ public class AchievementDbOperation
}
else
{
RemoveEntity(oldEntity);
AddEntity(newEntity);
appDbContext.Achievements.RemoveAndSave(oldEntity);
appDbContext.Achievements.AddAndSave(newEntity);
update++;
}
}
@@ -182,7 +183,7 @@ public class AchievementDbOperation
// entity.Id > uiaf.Id
moveOld = false;
moveNew = true;
AddEntity(newEntity);
appDbContext.Achievements.AddAndSave(newEntity);
add++;
}
}
@@ -196,16 +197,4 @@ public class AchievementDbOperation
return new(add, update, remove);
}
private void AddEntity(EntityAchievement entity)
{
appDbContext.Achievements.Add(entity);
appDbContext.SaveChanges();
}
private void RemoveEntity(EntityAchievement entity)
{
appDbContext.Achievements.Remove(entity);
appDbContext.SaveChanges();
}
}

View File

@@ -19,7 +19,7 @@ namespace Snap.Hutao.Service.Achievement;
/// <summary>
/// 成就服务
/// </summary>
[Injection(InjectAs.Transient, typeof(IAchievementService))]
[Injection(InjectAs.Scoped, typeof(IAchievementService))]
internal class AchievementService : IAchievementService
{
private readonly AppDbContext appDbContext;
@@ -40,7 +40,7 @@ internal class AchievementService : IAchievementService
this.appDbContext = appDbContext;
this.logger = logger;
dbCurrent = new(appDbContext, appDbContext.AchievementArchives, messenger);
dbCurrent = new(appDbContext.AchievementArchives, messenger);
achievementDbOperation = new(appDbContext);
}

View File

@@ -0,0 +1,95 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core;
using Snap.Hutao.Core.Threading;
using Snap.Hutao.Service.AppCenter.Model;
using Snap.Hutao.Service.AppCenter.Model.Log;
using Snap.Hutao.Web.Hoyolab;
using System.Net.Http;
namespace Snap.Hutao.Service.AppCenter;
[SuppressMessage("", "SA1600")]
[Injection(InjectAs.Singleton)]
public sealed class AppCenter : IDisposable
{
private const string AppSecret = "de5bfc48-17fc-47ee-8e7e-dee7dc59d554";
private const string API = "https://in.appcenter.ms/logs?api-version=1.0.0";
private readonly TaskCompletionSource uploadTaskCompletionSource = new();
private readonly CancellationTokenSource uploadTaskCancllationTokenSource = new();
private readonly HttpClient httpClient;
private readonly List<Log> queue;
private readonly Device deviceInfo;
private readonly JsonSerializerOptions options;
private Guid sessionID;
public AppCenter()
{
options = new(CoreEnvironment.JsonOptions);
options.Converters.Add(new LogConverter());
httpClient = new() { DefaultRequestHeaders = { { "Install-ID", CoreEnvironment.AppCenterDeviceId }, { "App-Secret", AppSecret } } };
queue = new List<Log>();
deviceInfo = new Device();
Task.Run(async () =>
{
while (!uploadTaskCancllationTokenSource.Token.IsCancellationRequested)
{
await UploadAsync().ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
}
uploadTaskCompletionSource.TrySetResult();
}).SafeForget();
}
public async Task UploadAsync()
{
if (queue.Count == 0)
{
return;
}
string? uploadStatus = null;
do
{
queue.ForEach(log => log.Status = LogStatus.Uploading);
LogContainer container = new(queue);
LogUploadResult? response = await httpClient
.TryCatchPostAsJsonAsync<LogContainer, LogUploadResult>(API, container, options)
.ConfigureAwait(false);
uploadStatus = response?.Status;
}
while (uploadStatus != "Success");
queue.RemoveAll(log => log.Status == LogStatus.Uploading);
}
public void Initialize()
{
sessionID = Guid.NewGuid();
queue.Add(new StartServiceLog("Analytics", "Crashes").Initialize(sessionID, deviceInfo));
queue.Add(new StartSessionLog().Initialize(sessionID, deviceInfo).Initialize(sessionID, deviceInfo));
}
public void TrackCrash(Exception exception, bool isFatal = true)
{
queue.Add(new ManagedErrorLog(exception, isFatal).Initialize(sessionID, deviceInfo));
}
public void TrackError(Exception exception)
{
queue.Add(new HandledErrorLog(exception).Initialize(sessionID, deviceInfo));
}
[SuppressMessage("", "VSTHRD002")]
public void Dispose()
{
uploadTaskCancllationTokenSource.Cancel();
uploadTaskCompletionSource.Task.GetAwaiter().GetResult();
}
}

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