mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e051787584 | ||
|
|
8f273e69b5 | ||
|
|
88037049f3 | ||
|
|
8357d3b971 | ||
|
|
d41acc0a77 | ||
|
|
0dd79d4206 | ||
|
|
86061e404f | ||
|
|
ee4197a18a | ||
|
|
c90e1ab8b8 | ||
|
|
de40947a7f | ||
|
|
542a0a4622 | ||
|
|
a718ba16e2 | ||
|
|
c0d670c5b6 |
@@ -1,5 +1,5 @@
|
||||
name: Artifact Rating Rules
|
||||
description: 圣遗物评分细则建议
|
||||
name: 圣遗物评分细则建议
|
||||
description: 为圣遗物评分规则提供你的想法
|
||||
title: "[Artifact Rating] 请在这里填写角色名称"
|
||||
labels: area-AvatarInfo
|
||||
assignees: Lightczx
|
||||
|
||||
@@ -8,6 +8,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**请在上方设置一个合适的工单标题**
|
||||
请按下方的要求填写完整的问题表单,以便我们更快的定位问题。
|
||||
|
||||
- type: input
|
||||
@@ -26,6 +27,27 @@ body:
|
||||
label: Snap Hutao 版本
|
||||
description: 在应用标题,应用程序的设置界面中靠下的位置可以找到
|
||||
placeholder: 例:1.1.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: user-set-category
|
||||
attributes:
|
||||
label: 问题分类
|
||||
description: 请设置一个你认为合适的分类,这将帮助我们快速定位问题
|
||||
options:
|
||||
- 安装和环境
|
||||
- 成就管理
|
||||
- 角色信息面板
|
||||
- 游戏启动
|
||||
- 实时便笺
|
||||
- 养成计算
|
||||
- 文件缓存
|
||||
- 祈愿记录
|
||||
- 玩家查询
|
||||
- 胡桃数据库
|
||||
- 用户界面
|
||||
- 公告
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -16,4 +16,4 @@ body:
|
||||
label: 你想要实现或优化的功能?
|
||||
description: 详细的描述一下你想要的功能
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
76
.github/ISSUE_TEMPLATE/network-issue.yml
vendored
Normal file
76
.github/ISSUE_TEMPLATE/network-issue.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: 网络问题
|
||||
description: 当网络问题影响到你的程序使用时
|
||||
title: "[Network]: 在这里填写一个合适的标题"
|
||||
labels: ["area-Network"]
|
||||
assignees:
|
||||
- Lightczx
|
||||
- Masterain98
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**请先在上方为工单设置一个合适的标题**
|
||||
**请按下方的要求填写完整的问题表单,以便我们更快的定位问题。**
|
||||
|
||||
- type: textarea
|
||||
id: network-diagnosis-report
|
||||
attributes:
|
||||
label: 提交你的网络诊断报告
|
||||
description: |
|
||||
停下!
|
||||
**在填写下面的问题之前请先使用我们的网络诊断工具**
|
||||
**这个工具将会生成一份报告,请将这份报告拖入下面的框中,让其与你的工单一起被上传提交**
|
||||
- 你可以点击下面的链接以下载网络诊断工具:
|
||||
- [胡桃资源站](https://d.hut.ao/d/tools/network-diagnosis-tool.exe)
|
||||
- [GitHub](https://github.com/DGP-Studio/Snap.Hutao/files/10081999/network-diagnosis-hutao.zip)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: user-geo-location
|
||||
attributes:
|
||||
label: 你的地理位置
|
||||
description: |
|
||||
中国用户请精确到省级行政区
|
||||
海外用户请精确到国家
|
||||
placeholder: 北京
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: user-isp
|
||||
attributes:
|
||||
label: 你的运营商
|
||||
description: 中国用户请精确到省级行政区,海外地区请精确到国家
|
||||
options:
|
||||
- 中国电信
|
||||
- 中国联通
|
||||
- 中国移动
|
||||
- 中国广电
|
||||
- 其它
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: user-issue-category
|
||||
attributes:
|
||||
label: 你的问题
|
||||
description: 选择一个问题类别
|
||||
options:
|
||||
- 完全无法连接服务器
|
||||
- 连接速度慢
|
||||
- 获取到了不正确的页面或数据
|
||||
- 图片下载错误(429 Error)
|
||||
- 其它
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: 你的问题(补充)
|
||||
description: 如果你在上一项中选择了`其它`或者你有更多信息需要提供,请在这里写下来
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
6
.github/workflows/PublishDistribution.yml
vendored
6
.github/workflows/PublishDistribution.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
||||
|
||||
# Download Publish.zip
|
||||
- name: Download Release
|
||||
timeout-minutes: 5
|
||||
uses: robinraju/release-downloader@v1.5
|
||||
with:
|
||||
repository: "DGP-Studio/Snap.Hutao"
|
||||
@@ -25,8 +26,9 @@ jobs:
|
||||
fileName: "*.zip"
|
||||
out-file-path: ./release-download
|
||||
|
||||
# Upload to OD21 (Testing)
|
||||
- name: Upload OD21
|
||||
# Upload to Drive
|
||||
- name: Upload Drive
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
RCCONF: ${{ secrets.RCCONF }}
|
||||
run: |
|
||||
|
||||
6
desktop.ini
Normal file
6
desktop.ini
Normal file
@@ -0,0 +1,6 @@
|
||||
[.ShellClassInfo]
|
||||
IconResource=D:\Develop\Projects\Snap.Hutao\src\Snap.Hutao\Snap.Hutao\Assets\Logo.ico,0
|
||||
[ViewState]
|
||||
Mode=
|
||||
Vid=
|
||||
FolderType=Generic
|
||||
@@ -1,4 +1,6 @@
|
||||
[*.cs]
|
||||
charset = utf-8-bom
|
||||
|
||||
[*.cs]
|
||||
|
||||
# SA1101: Prefix local calls with this
|
||||
dotnet_diagnostic.SA1101.severity = none
|
||||
@@ -76,31 +78,31 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# 命名样式
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_diagnostic.SA1629.severity = none
|
||||
dotnet_diagnostic.SA1642.severity = none
|
||||
@@ -185,29 +187,29 @@ dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||
dotnet_naming_symbols.类型.required_modifiers =
|
||||
dotnet_naming_symbols.类型.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||
dotnet_naming_symbols.非字段成员.required_modifiers =
|
||||
dotnet_naming_symbols.非字段成员.required_modifiers =
|
||||
|
||||
# 命名样式
|
||||
|
||||
dotnet_naming_style.以_i_开始.required_prefix = I
|
||||
dotnet_naming_style.以_i_开始.required_suffix =
|
||||
dotnet_naming_style.以_i_开始.word_separator =
|
||||
dotnet_naming_style.以_i_开始.required_suffix =
|
||||
dotnet_naming_style.以_i_开始.word_separator =
|
||||
dotnet_naming_style.以_i_开始.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.帕斯卡拼写法.required_prefix =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_suffix =
|
||||
dotnet_naming_style.帕斯卡拼写法.word_separator =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_prefix =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_suffix =
|
||||
dotnet_naming_style.帕斯卡拼写法.word_separator =
|
||||
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.帕斯卡拼写法.required_prefix =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_suffix =
|
||||
dotnet_naming_style.帕斯卡拼写法.word_separator =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_prefix =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_suffix =
|
||||
dotnet_naming_style.帕斯卡拼写法.word_separator =
|
||||
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
|
||||
|
||||
@@ -46,6 +46,7 @@ public class HttpClientGenerator : ISourceGenerator
|
||||
string toolName = this.GetGeneratorType().FullName;
|
||||
|
||||
StringBuilder sourceCodeBuilder = new();
|
||||
|
||||
sourceCodeBuilder.Append($@"// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
@@ -80,10 +81,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
|
||||
foreach (INamedTypeSymbol classSymbol in receiver.Classes)
|
||||
{
|
||||
lineBuilder
|
||||
.Clear()
|
||||
.Append("\r\n");
|
||||
|
||||
lineBuilder.Clear().Append(Environment.NewLine);
|
||||
lineBuilder.Append(@" services.AddHttpClient<");
|
||||
lineBuilder.Append($"{classSymbol.ToDisplayString()}>(");
|
||||
|
||||
|
||||
@@ -41,8 +41,10 @@
|
||||
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
|
||||
<shmmc:DescParamDescriptor x:Key="DescParamDescriptor"/>
|
||||
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
|
||||
<shmmc:EquipIconConverter x:Key="EquipIconConverter"/>
|
||||
<shmmc:GachaAvatarImgConverter x:Key="GachaAvatarImgConverter"/>
|
||||
<shmmc:GachaAvatarIconConverter x:Key="GachaAvatarIconConverter"/>
|
||||
<shmmc:GachaEquipIconConverter x:Key="GachaEquipIconConverter"/>
|
||||
<shmmc:ItemIconConverter x:Key="ItemIconConverter"/>
|
||||
<shmmc:PropertyInfoDescriptor x:Key="PropertyDescriptor"/>
|
||||
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
|
||||
|
||||
@@ -10,8 +10,11 @@ namespace Snap.Hutao.Context.Database;
|
||||
/// <summary>
|
||||
/// 应用程序数据库上下文
|
||||
/// </summary>
|
||||
public class AppDbContext : DbContext
|
||||
public sealed class AppDbContext : DbContext
|
||||
{
|
||||
private readonly Guid contextId;
|
||||
private readonly ILogger<AppDbContext>? logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的应用程序数据库上下文
|
||||
/// </summary>
|
||||
@@ -21,6 +24,19 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的应用程序数据库上下文
|
||||
/// </summary>
|
||||
/// <param name="options">选项</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options, ILogger<AppDbContext> logger)
|
||||
: this(options)
|
||||
{
|
||||
contextId = Guid.NewGuid();
|
||||
this.logger = logger;
|
||||
logger.LogInformation("AppDbContext[{id}] created.", contextId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置
|
||||
/// </summary>
|
||||
@@ -66,6 +82,11 @@ public class AppDbContext : DbContext
|
||||
/// </summary>
|
||||
public DbSet<DailyNoteEntry> DailyNotes { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 对象缓存
|
||||
/// </summary>
|
||||
public DbSet<ObjectCacheEntry> ObjectCache { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个临时的应用程序数据库上下文
|
||||
/// </summary>
|
||||
@@ -76,6 +97,13 @@ public class AppDbContext : DbContext
|
||||
return new(new DbContextOptionsBuilder<AppDbContext>().UseSqlite(sqlConnectionString).Options);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
logger?.LogInformation("AppDbContext[{id}] disposed.", contextId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
|
||||
@@ -32,4 +32,4 @@ internal class InvokeCommandOnUnloadedBehavior : BehaviorBase<UIElement>
|
||||
|
||||
base.OnDetaching();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ internal static class ContentDialogExtensions
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
contentDialog.ShowAsync().AsTask().SafeForget();
|
||||
|
||||
// E_ASYNC_OPERATION_NOT_STARTED 0x80000019
|
||||
return new ContentDialogHider(contentDialog);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
namespace Snap.Hutao.Core.Caching;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods and tools to cache files in a folder
|
||||
/// 经过简化
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Generic type as supplied by consumer of the class</typeparam>
|
||||
[SuppressMessage("", "CA1001")]
|
||||
public abstract class CacheBase<T>
|
||||
where T : class
|
||||
{
|
||||
private readonly SemaphoreSlim cacheFolderSemaphore = new(1);
|
||||
private readonly ILogger logger;
|
||||
|
||||
// violate di rule
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private StorageFolder? baseFolder;
|
||||
private string? cacheFolderName;
|
||||
private StorageFolder? cacheFolder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CacheBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="httpClient">http客户端</param>
|
||||
protected CacheBase(ILogger logger, HttpClient httpClient)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.httpClient = httpClient;
|
||||
|
||||
CacheDuration = TimeSpan.FromDays(30);
|
||||
RetryCount = 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the life duration of every cache entry.
|
||||
/// </summary>
|
||||
public TimeSpan CacheDuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of retries trying to ensure the file is cached.
|
||||
/// </summary>
|
||||
public uint RetryCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears all files in the cache
|
||||
/// </summary>
|
||||
/// <returns>awaitable task</returns>
|
||||
public async Task ClearAsync()
|
||||
{
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
await RemoveAsync(files).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes cached files that have expired
|
||||
/// </summary>
|
||||
/// <param name="duration">Optional timespan to compute whether file has expired or not. If no value is supplied, <see cref="CacheDuration"/> is used.</param>
|
||||
/// <returns>awaitable task</returns>
|
||||
public async Task RemoveExpiredAsync(TimeSpan? duration = null)
|
||||
{
|
||||
TimeSpan expiryDuration = duration ?? CacheDuration;
|
||||
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
List<StorageFile> filesToDelete = new();
|
||||
|
||||
foreach (StorageFile file in files)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await IsFileOutOfDateAsync(file, expiryDuration, false).ConfigureAwait(false))
|
||||
{
|
||||
filesToDelete.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
await RemoveAsync(filesToDelete).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removed items based on uri list passed
|
||||
/// </summary>
|
||||
/// <param name="uriForCachedItems">Enumerable uri list</param>
|
||||
/// <returns>awaitable Task</returns>
|
||||
public async Task RemoveAsync(IEnumerable<Uri> uriForCachedItems)
|
||||
{
|
||||
if (uriForCachedItems == null || !uriForCachedItems.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
List<StorageFile> filesToDelete = new();
|
||||
|
||||
Dictionary<string, StorageFile> cachedFiles = files.ToDictionary(file => file.Name);
|
||||
|
||||
foreach (Uri uri in uriForCachedItems)
|
||||
{
|
||||
string fileName = GetCacheFileName(uri);
|
||||
if (cachedFiles.TryGetValue(fileName, out StorageFile? file))
|
||||
{
|
||||
filesToDelete.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
await RemoveAsync(filesToDelete).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StorageFile containing cached item for given Uri
|
||||
/// </summary>
|
||||
/// <param name="uri">Uri of the item.</param>
|
||||
/// <returns>a StorageFile</returns>
|
||||
public async Task<StorageFile> GetFileFromCacheAsync(Uri uri)
|
||||
{
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
|
||||
string fileName = GetCacheFileName(uri);
|
||||
|
||||
IStorageItem? item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
|
||||
if (item == null || (await item.GetBasicPropertiesAsync()).Size == 0)
|
||||
{
|
||||
StorageFile baseFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false);
|
||||
await DownloadFileAsync(uri, baseFile).ConfigureAwait(false);
|
||||
item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Must.NotNull((item as StorageFile)!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override-able method that checks whether file is valid or not.
|
||||
/// </summary>
|
||||
/// <param name="file">storage file</param>
|
||||
/// <param name="duration">cache duration</param>
|
||||
/// <param name="treatNullFileAsOutOfDate">option to mark uninitialized file as expired</param>
|
||||
/// <returns>bool indicate whether file has expired or not</returns>
|
||||
protected virtual async Task<bool> IsFileOutOfDateAsync(StorageFile file, TimeSpan duration, bool treatNullFileAsOutOfDate = true)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
return treatNullFileAsOutOfDate;
|
||||
}
|
||||
|
||||
BasicProperties? properties = await file.GetBasicPropertiesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
return properties.Size == 0 || DateTime.Now.Subtract(properties.DateModified.DateTime) > duration;
|
||||
}
|
||||
|
||||
private static string GetCacheFileName(Uri uri)
|
||||
{
|
||||
string url = uri.ToString();
|
||||
byte[] chars = Encoding.UTF8.GetBytes(url);
|
||||
byte[] hash = SHA1.HashData(chars);
|
||||
return System.Convert.ToHexString(hash);
|
||||
}
|
||||
|
||||
private async Task DownloadFileAsync(Uri uri, StorageFile baseFile)
|
||||
{
|
||||
logger.LogInformation(EventIds.FileCaching, "Begin downloading for {uri}", uri);
|
||||
|
||||
using (Stream httpStream = await httpClient.GetStreamAsync(uri).ConfigureAwait(false))
|
||||
{
|
||||
using (FileStream fileStream = File.Create(baseFile.Path))
|
||||
{
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with default values if user has not initialized explicitly
|
||||
/// </summary>
|
||||
/// <returns>awaitable task</returns>
|
||||
private async Task InitializeInternalAsync()
|
||||
{
|
||||
if (cacheFolder != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (await cacheFolderSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
baseFolder ??= ApplicationData.Current.TemporaryFolder;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cacheFolderName))
|
||||
{
|
||||
cacheFolderName = GetType().Name;
|
||||
}
|
||||
|
||||
cacheFolder = await baseFolder
|
||||
.CreateFolderAsync(cacheFolderName, CreationCollisionOption.OpenIfExists)
|
||||
.AsTask()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<StorageFolder> GetCacheFolderAsync()
|
||||
{
|
||||
if (cacheFolder == null)
|
||||
{
|
||||
await InitializeInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Must.NotNull(cacheFolder!);
|
||||
}
|
||||
|
||||
private async Task RemoveAsync(IEnumerable<StorageFile> files)
|
||||
{
|
||||
foreach (StorageFile file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogInformation(EventIds.CacheRemoveFile, "Removing file {file}", file.Path);
|
||||
await file.DeleteAsync().AsTask().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogError(EventIds.CacheException, "Failed to delete file: {file}", file.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
@@ -16,20 +21,159 @@ namespace Snap.Hutao.Core.Caching;
|
||||
[Injection(InjectAs.Singleton, typeof(IImageCache))]
|
||||
[HttpClient(HttpClientConfigration.Default)]
|
||||
[PrimaryHttpMessageHandler(MaxConnectionsPerServer = 16)]
|
||||
public class ImageCache : CacheBase<BitmapImage>, IImageCache
|
||||
[SuppressMessage("", "CA1001")]
|
||||
public class ImageCache : IImageCache
|
||||
{
|
||||
private const string DateAccessedProperty = "System.DateAccessed";
|
||||
|
||||
private static readonly ImmutableDictionary<int, TimeSpan> RetryCountToDelay = new Dictionary<int, TimeSpan>()
|
||||
{
|
||||
[0] = TimeSpan.FromSeconds(4),
|
||||
[1] = TimeSpan.FromSeconds(16),
|
||||
[2] = TimeSpan.FromSeconds(64),
|
||||
[3] = TimeSpan.FromSeconds(4),
|
||||
[4] = TimeSpan.FromSeconds(16),
|
||||
[5] = TimeSpan.FromSeconds(64),
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
private readonly List<string> extendedPropertyNames = new() { DateAccessedProperty };
|
||||
|
||||
private readonly SemaphoreSlim cacheFolderSemaphore = new(1);
|
||||
private readonly ILogger logger;
|
||||
|
||||
// violate di rule
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private StorageFolder? baseFolder;
|
||||
private string? cacheFolderName;
|
||||
private StorageFolder? cacheFolder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCache"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="httpClientFactory">http客户端工厂</param>
|
||||
public ImageCache(ILogger<ImageCache> logger, IHttpClientFactory httpClientFactory)
|
||||
: base(logger, httpClientFactory.CreateClient(nameof(ImageCache)))
|
||||
{
|
||||
this.logger = logger;
|
||||
httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
|
||||
|
||||
CacheDuration = TimeSpan.FromDays(30);
|
||||
RetryCount = 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the life duration of every cache entry.
|
||||
/// </summary>
|
||||
public TimeSpan CacheDuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of retries trying to ensure the file is cached.
|
||||
/// </summary>
|
||||
public uint RetryCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears all files in the cache
|
||||
/// </summary>
|
||||
/// <returns>awaitable task</returns>
|
||||
public async Task ClearAsync()
|
||||
{
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
await RemoveAsync(files).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes cached files that have expired
|
||||
/// </summary>
|
||||
/// <param name="duration">Optional timespan to compute whether file has expired or not. If no value is supplied, <see cref="CacheDuration"/> is used.</param>
|
||||
/// <returns>awaitable task</returns>
|
||||
public async Task RemoveExpiredAsync(TimeSpan? duration = null)
|
||||
{
|
||||
TimeSpan expiryDuration = duration ?? CacheDuration;
|
||||
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
List<StorageFile> filesToDelete = new();
|
||||
|
||||
foreach (StorageFile file in files)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await IsFileOutOfDateAsync(file, expiryDuration, false).ConfigureAwait(false))
|
||||
{
|
||||
filesToDelete.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
await RemoveAsync(filesToDelete).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removed items based on uri list passed
|
||||
/// </summary>
|
||||
/// <param name="uriForCachedItems">Enumerable uri list</param>
|
||||
/// <returns>awaitable Task</returns>
|
||||
public async Task RemoveAsync(IEnumerable<Uri> uriForCachedItems)
|
||||
{
|
||||
if (uriForCachedItems == null || !uriForCachedItems.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
IReadOnlyList<StorageFile> files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false);
|
||||
|
||||
List<StorageFile> filesToDelete = new();
|
||||
|
||||
Dictionary<string, StorageFile> cachedFiles = files.ToDictionary(file => file.Name);
|
||||
|
||||
foreach (Uri uri in uriForCachedItems)
|
||||
{
|
||||
string fileName = GetCacheFileName(uri);
|
||||
if (cachedFiles.TryGetValue(fileName, out StorageFile? file))
|
||||
{
|
||||
filesToDelete.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
await RemoveAsync(filesToDelete).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StorageFile containing cached item for given Uri
|
||||
/// </summary>
|
||||
/// <param name="uri">Uri of the item.</param>
|
||||
/// <returns>a StorageFile</returns>
|
||||
public async Task<StorageFile> GetFileFromCacheAsync(Uri uri)
|
||||
{
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
|
||||
string fileName = GetCacheFileName(uri);
|
||||
|
||||
IStorageItem? item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
|
||||
if (item == null || (await item.GetBasicPropertiesAsync()).Size == 0)
|
||||
{
|
||||
StorageFile baseFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false);
|
||||
await DownloadFileAsync(uri, baseFile).ConfigureAwait(false);
|
||||
item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Must.NotNull((item as StorageFile)!);
|
||||
}
|
||||
|
||||
private static string GetCacheFileName(Uri uri)
|
||||
{
|
||||
string url = uri.ToString();
|
||||
byte[] chars = Encoding.UTF8.GetBytes(url);
|
||||
byte[] hash = SHA1.HashData(chars);
|
||||
return System.Convert.ToHexString(hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,7 +183,7 @@ public class ImageCache : CacheBase<BitmapImage>, IImageCache
|
||||
/// <param name="duration">cache duration</param>
|
||||
/// <param name="treatNullFileAsOutOfDate">option to mark uninitialized file as expired</param>
|
||||
/// <returns>bool indicate whether file has expired or not</returns>
|
||||
protected override async Task<bool> IsFileOutOfDateAsync(StorageFile file, TimeSpan duration, bool treatNullFileAsOutOfDate = true)
|
||||
private async Task<bool> IsFileOutOfDateAsync(StorageFile file, TimeSpan duration, bool treatNullFileAsOutOfDate = true)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
@@ -72,4 +216,93 @@ public class ImageCache : CacheBase<BitmapImage>, IImageCache
|
||||
|
||||
return properties.Size == 0 || DateTime.Now.Subtract(properties.DateModified.DateTime) > duration;
|
||||
}
|
||||
|
||||
private async Task DownloadFileAsync(Uri uri, StorageFile baseFile)
|
||||
{
|
||||
logger.LogInformation(EventIds.FileCaching, "Begin downloading for {uri}", uri);
|
||||
|
||||
int retryCount = 0;
|
||||
while (retryCount < 6)
|
||||
{
|
||||
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
if (message.IsSuccessStatusCode)
|
||||
{
|
||||
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
using (FileStream fileStream = File.Create(baseFile.Path))
|
||||
{
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (message.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? RetryCountToDelay[retryCount];
|
||||
logger.LogInformation("Retry after {delay}.", delay);
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (retryCount == 3)
|
||||
{
|
||||
uri = new UriBuilder(uri) { Host = "static.hut.ao", }.Uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with default values if user has not initialized explicitly
|
||||
/// </summary>
|
||||
/// <returns>awaitable task</returns>
|
||||
private async Task InitializeInternalAsync()
|
||||
{
|
||||
if (cacheFolder != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (await cacheFolderSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
baseFolder ??= ApplicationData.Current.TemporaryFolder;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cacheFolderName))
|
||||
{
|
||||
cacheFolderName = GetType().Name;
|
||||
}
|
||||
|
||||
cacheFolder = await baseFolder
|
||||
.CreateFolderAsync(cacheFolderName, CreationCollisionOption.OpenIfExists)
|
||||
.AsTask()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<StorageFolder> GetCacheFolderAsync()
|
||||
{
|
||||
if (cacheFolder == null)
|
||||
{
|
||||
await InitializeInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return Must.NotNull(cacheFolder!);
|
||||
}
|
||||
|
||||
private async Task RemoveAsync(IEnumerable<StorageFile> files)
|
||||
{
|
||||
foreach (StorageFile file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogInformation(EventIds.CacheRemoveFile, "Removing file {file}", file.Path);
|
||||
await file.DeleteAsync().AsTask().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogError(EventIds.CacheException, "Failed to delete file: {file}", file.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,13 @@ public static class CastTo<TTo>
|
||||
|
||||
private static Func<TCachedFrom, TTo> Get()
|
||||
{
|
||||
// 参数表达式,表示 传入源类型
|
||||
ParameterExpression param = Expression.Parameter(typeof(TCachedFrom));
|
||||
|
||||
// 一元转换 调用 相关类的显式或隐式转换运算符
|
||||
UnaryExpression convert = Expression.ConvertChecked(param, typeof(TTo));
|
||||
|
||||
// 生成一个源类型入,目标类型出的 lamdba
|
||||
return Expression.Lambda<Func<TCachedFrom, TTo>>(convert, param).Compile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ internal static class CoreEnvironment
|
||||
/// </summary>
|
||||
public const string HoyolabUA = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/{HoyolabXrpcVersion}";
|
||||
|
||||
/// <summary>
|
||||
/// 米游社移动端请求UA
|
||||
/// </summary>
|
||||
public const string HoyolabMobileUA = $"Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Mobile Safari/537.36 miHoYoBBS/{HoyolabXrpcVersion}";
|
||||
|
||||
/// <summary>
|
||||
/// 米游社 Rpc 版本
|
||||
/// </summary>
|
||||
|
||||
@@ -23,6 +23,13 @@ internal class JsonTextEncoder : JavaScriptEncoder
|
||||
/// <inheritdoc/>
|
||||
public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten)
|
||||
{
|
||||
// " => \"
|
||||
if (unicodeScalar == '"')
|
||||
{
|
||||
numberOfCharactersWritten = 2;
|
||||
return "\\\"".AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
|
||||
}
|
||||
|
||||
string encoded = $"\\u{(uint)unicodeScalar:x4}";
|
||||
numberOfCharactersWritten = (encoded.Length <= (uint)bufferLength) ? encoded.Length : 0;
|
||||
return encoded.AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
|
||||
@@ -31,6 +38,6 @@ internal class JsonTextEncoder : JavaScriptEncoder
|
||||
/// <inheritdoc/>
|
||||
public override bool WillEncode(int unicodeScalar)
|
||||
{
|
||||
return true;
|
||||
return unicodeScalar == '=';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32.TaskScheduler;
|
||||
using System.Runtime.InteropServices;
|
||||
using SchedulerTask = Microsoft.Win32.TaskScheduler.Task;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
@@ -22,27 +23,26 @@ internal static class TaskSchedulerHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
TimeSpan intervalTime = TimeSpan.FromSeconds(interval);
|
||||
if (TaskService.Instance.GetTask(DailyNoteRefreshTaskName) is SchedulerTask targetTask)
|
||||
SchedulerTask? targetTask = TaskService.Instance.GetTask(DailyNoteRefreshTaskName);
|
||||
if (targetTask != null)
|
||||
{
|
||||
TimeTrigger? trigger = targetTask.Definition.Triggers[0] as TimeTrigger;
|
||||
trigger!.Repetition.Interval = intervalTime;
|
||||
targetTask.RegisterChanges();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskDefinition task = TaskService.Instance.NewTask();
|
||||
task.RegistrationInfo.Description = "胡桃实时便笺刷新任务 | 请勿编辑或删除。";
|
||||
task.Triggers.Add(new TimeTrigger() { Repetition = new(intervalTime, TimeSpan.Zero), });
|
||||
task.Actions.Add("explorer", "hutao://DailyNote/Refresh");
|
||||
TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task);
|
||||
return true;
|
||||
TaskService.Instance.RootFolder.DeleteTask(DailyNoteRefreshTaskName);
|
||||
}
|
||||
|
||||
TaskDefinition task = TaskService.Instance.NewTask();
|
||||
task.RegistrationInfo.Description = "胡桃实时便笺刷新任务 | 请勿编辑或删除。";
|
||||
task.Triggers.Add(new TimeTrigger() { Repetition = new(TimeSpan.FromSeconds(interval), TimeSpan.Zero), });
|
||||
task.Actions.Add("explorer", "hutao://DailyNote/Refresh");
|
||||
TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task);
|
||||
return true;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,19 +38,6 @@ public static class Must
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务异常
|
||||
/// </summary>
|
||||
/// <typeparam name="T">任务结果类型</typeparam>
|
||||
/// <param name="message">异常消息</param>
|
||||
/// <returns>异常的任务</returns>
|
||||
[SuppressMessage("", "VSTHRD200")]
|
||||
public static Task<T> Fault<T>(string message)
|
||||
{
|
||||
InvalidOperationException exception = new(message);
|
||||
return Task.FromException<T>(exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unconditionally throws an <see cref="NotSupportedException"/>.
|
||||
/// </summary>
|
||||
|
||||
25
src/Snap.Hutao/Snap.Hutao/Extension/LoggerExtension.cs
Normal file
25
src/Snap.Hutao/Snap.Hutao/Extension/LoggerExtension.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// 日志器扩展
|
||||
/// </summary>
|
||||
[SuppressMessage("", "CA2254")]
|
||||
public static class LoggerExtension
|
||||
{
|
||||
/// <inheritdoc cref="LoggerExtensions.LogInformation(ILogger, string?, object?[])"/>
|
||||
public static T LogInformation<T>(this ILogger logger, string message, params object?[] param)
|
||||
{
|
||||
logger.LogInformation(message, param);
|
||||
return default!;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="LoggerExtensions.LogWarning(ILogger, string?, object?[])"/>
|
||||
public static T LogWarning<T>(this ILogger logger, string message, params object?[] param)
|
||||
{
|
||||
logger.LogWarning(message, param);
|
||||
return default!;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,18 @@ namespace Snap.Hutao.Extension;
|
||||
/// </summary>
|
||||
public static class StringBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 当条件符合时执行 <see cref="StringBuilder.Append(string?)"/>
|
||||
/// </summary>
|
||||
/// <param name="sb">字符串建造器</param>
|
||||
/// <param name="condition">条件</param>
|
||||
/// <param name="value">附加的字符</param>
|
||||
/// <returns>同一个字符串建造器</returns>
|
||||
public static StringBuilder AppendIf(this StringBuilder sb, bool condition, char? value)
|
||||
{
|
||||
return condition ? sb.Append(value) : sb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当条件符合时执行 <see cref="StringBuilder.Append(string?)"/>
|
||||
/// </summary>
|
||||
@@ -34,4 +46,4 @@ public static class StringBuilderExtensions
|
||||
{
|
||||
return condition ? sb.Append(trueValue) : sb.Append(falseValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@ internal interface IPickerFactory
|
||||
/// <summary>
|
||||
/// 获取 经过初始化的 <see cref="FileOpenPicker"/>
|
||||
/// </summary>
|
||||
/// <param name="location">初始位置</param>
|
||||
/// <param name="commitButton">提交按钮文本</param>
|
||||
/// <param name="fileTypes">文件类型</param>
|
||||
/// <returns>经过初始化的 <see cref="FileOpenPicker"/></returns>
|
||||
FileOpenPicker GetFileOpenPicker();
|
||||
FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes);
|
||||
|
||||
/// <summary>
|
||||
/// 获取 经过初始化的 <see cref="FileSavePicker"/>
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Snap.Hutao.Factory;
|
||||
[Injection(InjectAs.Transient, typeof(IPickerFactory))]
|
||||
internal class PickerFactory : IPickerFactory
|
||||
{
|
||||
private const string AnyType = "*";
|
||||
|
||||
private readonly MainWindow mainWindow;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,9 +25,21 @@ internal class PickerFactory : IPickerFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FileOpenPicker GetFileOpenPicker()
|
||||
public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes)
|
||||
{
|
||||
return GetInitializedPicker<FileOpenPicker>();
|
||||
FileOpenPicker picker = GetInitializedPicker<FileOpenPicker>();
|
||||
|
||||
picker.SuggestedStartLocation = location;
|
||||
picker.CommitButtonText = commitButton;
|
||||
|
||||
foreach (string type in fileTypes)
|
||||
{
|
||||
picker.FileTypeFilter.Add(type);
|
||||
}
|
||||
|
||||
picker.FileTypeFilter.Add(AnyType);
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
292
src/Snap.Hutao/Snap.Hutao/Migrations/20221123060511_RenameCookieToLtoken.Designer.cs
generated
Normal file
292
src/Snap.Hutao/Snap.Hutao/Migrations/20221123060511_RenameCookieToLtoken.Designer.cs
generated
Normal file
@@ -0,0 +1,292 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Context.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20221123060511_RenameCookieToLtoken")]
|
||||
partial class RenameCookieToLtoken
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Ltoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Stoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RenameCookieToLtoken : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Cookie",
|
||||
table: "users",
|
||||
newName: "Ltoken");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Ltoken",
|
||||
table: "users",
|
||||
newName: "Cookie");
|
||||
}
|
||||
}
|
||||
}
|
||||
295
src/Snap.Hutao/Snap.Hutao/Migrations/20221123110240_AddCookieToken.Designer.cs
generated
Normal file
295
src/Snap.Hutao/Snap.Hutao/Migrations/20221123110240_AddCookieToken.Designer.cs
generated
Normal file
@@ -0,0 +1,295 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Context.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20221123110240_AddCookieToken")]
|
||||
partial class AddCookieToken
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CookieToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Ltoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Stoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCookieToken : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CookieToken",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CookieToken",
|
||||
table: "users");
|
||||
}
|
||||
}
|
||||
}
|
||||
311
src/Snap.Hutao/Snap.Hutao/Migrations/20221128115346_ObjectCache.Designer.cs
generated
Normal file
311
src/Snap.Hutao/Snap.Hutao/Migrations/20221128115346_ObjectCache.Designer.cs
generated
Normal file
@@ -0,0 +1,311 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Context.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20221128115346_ObjectCache")]
|
||||
partial class ObjectCache
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpireTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("object_cache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CookieToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Ltoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Stoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ObjectCache : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "object_cache",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ExpireTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||
Value = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_object_cache", x => x.Key);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "object_cache");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,6 +212,22 @@ namespace Snap.Hutao.Migrations
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpireTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("object_cache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
@@ -234,12 +250,15 @@ namespace Snap.Hutao.Migrations
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Cookie")
|
||||
b.Property<string>("CookieToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Ltoken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
||||
@@ -41,6 +41,14 @@ public class TypedWishSummary : WishBase
|
||||
/// </summary>
|
||||
public int LastOrangePull { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 据上个五星抽数格式化
|
||||
/// </summary>
|
||||
public string LastOrangePullFormatted
|
||||
{
|
||||
get => $"已垫 {LastOrangePull} 抽";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 五星保底阈值
|
||||
/// </summary>
|
||||
@@ -51,6 +59,14 @@ public class TypedWishSummary : WishBase
|
||||
/// </summary>
|
||||
public int LastPurplePull { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 据上个四星抽数格式化
|
||||
/// </summary>
|
||||
public string LastPurplePullFormatted
|
||||
{
|
||||
get => $"已垫 {LastPurplePull} 抽";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 四星保底阈值
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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;
|
||||
@@ -9,18 +8,8 @@ namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
/// <summary>
|
||||
/// 角色搭配
|
||||
/// </summary>
|
||||
public class ComplexAvatarCollocation : ComplexAvatar
|
||||
public class ComplexAvatarCollocation
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的角色搭配
|
||||
/// </summary>
|
||||
/// <param name="avatar">角色</param>
|
||||
/// <param name="rate">比率</param>
|
||||
public ComplexAvatarCollocation(Avatar avatar)
|
||||
: base(avatar, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
using System.Text;
|
||||
|
||||
@@ -17,7 +18,7 @@ public class ComplexReliquarySet
|
||||
/// </summary>
|
||||
/// <param name="reliquarySetRate">圣遗物套装率</param>
|
||||
/// <param name="idReliquarySetMap">圣遗物套装映射</param>
|
||||
public ComplexReliquarySet(ItemRate<ReliquarySets, double> reliquarySetRate, Dictionary<int, Metadata.Reliquary.ReliquarySet> idReliquarySetMap)
|
||||
public ComplexReliquarySet(ItemRate<ReliquarySets, double> reliquarySetRate, Dictionary<EquipAffixId, Metadata.Reliquary.ReliquarySet> idReliquarySetMap)
|
||||
{
|
||||
ReliquarySets sets = reliquarySetRate.Item;
|
||||
|
||||
@@ -43,7 +44,7 @@ public class ComplexReliquarySet
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = "无圣遗物";
|
||||
Name = "无圣遗物或散件";
|
||||
}
|
||||
|
||||
Rate = $"{reliquarySetRate.Rate:P3}";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
@@ -16,7 +17,7 @@ internal class ComplexTeamRank
|
||||
/// </summary>
|
||||
/// <param name="teamRank">队伍排行</param>
|
||||
/// <param name="idAvatarMap">映射</param>
|
||||
public ComplexTeamRank(TeamAppearance teamRank, Dictionary<int, Avatar> idAvatarMap)
|
||||
public ComplexTeamRank(TeamAppearance teamRank, Dictionary<AvatarId, Avatar> idAvatarMap)
|
||||
{
|
||||
Floor = $"第 {teamRank.Floor} 层";
|
||||
Up = teamRank.Up.Select(teamRate => new Team(teamRate, idAvatarMap)).ToList();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 武器搭配
|
||||
/// </summary>
|
||||
public class ComplexWeaponCollocation
|
||||
{
|
||||
/// <summary>
|
||||
/// 武器Id
|
||||
/// </summary>
|
||||
public WeaponId WeaponId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public List<ComplexAvatar> Avatars { get; set; } = default!;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.Hutao;
|
||||
@@ -16,10 +17,10 @@ internal class Team : List<ComplexAvatar>
|
||||
/// </summary>
|
||||
/// <param name="team">队伍</param>
|
||||
/// <param name="idAvatarMap">映射</param>
|
||||
public Team(ItemRate<string, int> team, Dictionary<int, Avatar> idAvatarMap)
|
||||
public Team(ItemRate<string, int> team, Dictionary<AvatarId, Avatar> idAvatarMap)
|
||||
: base(4)
|
||||
{
|
||||
IEnumerable<int> ids = team.Item.Split(',').Select(i => int.Parse(i));
|
||||
IEnumerable<int> ids = team.Item.Split(',').Select(int.Parse);
|
||||
|
||||
foreach (int id in ids)
|
||||
{
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Passport;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using EntityUser = Snap.Hutao.Model.Entity.User;
|
||||
|
||||
@@ -41,12 +43,12 @@ public class User : ObservableObject
|
||||
public List<UserGameRole> UserGameRoles { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// 用户信息, 请勿访问set
|
||||
/// </summary>
|
||||
public UserGameRole? SelectedUserGameRole
|
||||
{
|
||||
get => selectedUserGameRole;
|
||||
private set => SetProperty(ref selectedUserGameRole, value);
|
||||
set => SetProperty(ref selectedUserGameRole, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.IsSelected"/>
|
||||
@@ -56,30 +58,25 @@ public class User : ObservableObject
|
||||
set => inner.IsSelected = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.Cookie"/>
|
||||
public Cookie? Cookie
|
||||
/// <inheritdoc cref="EntityUser.CookieToken"/>
|
||||
public Cookie? CookieToken
|
||||
{
|
||||
get => inner.Cookie;
|
||||
set => inner.Cookie = value;
|
||||
get => inner.CookieToken;
|
||||
set => inner.CookieToken = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.Ltoken"/>
|
||||
public Cookie? Ltoken
|
||||
{
|
||||
get => inner.Ltoken;
|
||||
set => inner.Ltoken = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityUser.Stoken"/>
|
||||
public Cookie? Stoken
|
||||
{
|
||||
get => inner.Cookie;
|
||||
set
|
||||
{
|
||||
inner.Stoken = value;
|
||||
OnPropertyChanged(nameof(HasStoken));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否拥有 SToken
|
||||
/// </summary>
|
||||
public bool HasStoken
|
||||
{
|
||||
get => inner.Stoken != null;
|
||||
get => inner.Stoken;
|
||||
set => inner.Stoken = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,11 +84,6 @@ public class User : ObservableObject
|
||||
/// </summary>
|
||||
public EntityUser Entity { get => inner; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否初始化完成
|
||||
/// </summary>
|
||||
public bool IsInitialized { get => isInitialized; }
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库恢复用户
|
||||
/// </summary>
|
||||
@@ -101,8 +93,14 @@ public class User : ObservableObject
|
||||
internal static async Task<User?> ResumeAsync(EntityUser inner, CancellationToken token = default)
|
||||
{
|
||||
User user = new(inner);
|
||||
bool successful = await user.InitializeCoreAsync(token).ConfigureAwait(false);
|
||||
return successful ? user : null;
|
||||
bool isOk = await user.InitializeCoreAsync(token).ConfigureAwait(false);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
user.UserInfo = new UserInfo() { Nickname = "网络异常" };
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -113,36 +111,23 @@ public class User : ObservableObject
|
||||
/// <returns>用户是否初始化完成,若Cookie失效会返回 <see langword="null"/> </returns>
|
||||
internal static async Task<User?> CreateAsync(Cookie cookie, CancellationToken token = default)
|
||||
{
|
||||
// 这里只负责创建实体用户,稍后在用户服务中保存到数据库
|
||||
EntityUser entity = EntityUser.Create(cookie);
|
||||
|
||||
UserInformation? userInfo = await Ioc.Default
|
||||
.GetRequiredService<PassportClient>()
|
||||
.VerifyLtokenAsync(cookie, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
entity.Aid = cookie.GetValueOrDefault(Cookie.STUID);
|
||||
entity.Mid = cookie.GetValueOrDefault(Cookie.MID);
|
||||
|
||||
entity.Aid = userInfo?.Aid;
|
||||
entity.Mid = userInfo?.Mid;
|
||||
|
||||
if (entity.Aid == null && entity.Mid == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
if (entity.Aid != null && entity.Mid != null)
|
||||
{
|
||||
User user = new(entity);
|
||||
bool initialized = await user.InitializeCoreAsync(token).ConfigureAwait(false);
|
||||
|
||||
return initialized ? user : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新SToken
|
||||
/// </summary>
|
||||
/// <param name="stoken">cookie</param>
|
||||
internal void UpdateSToken(Cookie stoken)
|
||||
{
|
||||
Stoken = stoken;
|
||||
OnPropertyChanged(nameof(HasStoken));
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> InitializeCoreAsync(CancellationToken token = default)
|
||||
@@ -152,13 +137,58 @@ public class User : ObservableObject
|
||||
return true;
|
||||
}
|
||||
|
||||
UserInfo = await Ioc.Default.GetRequiredService<UserClient>()
|
||||
.GetUserFullInfoAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
if (Stoken == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UserGameRoles = await Ioc.Default.GetRequiredService<BindingClient>()
|
||||
.GetUserGameRolesByCookieAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
using (IServiceScope scope = Ioc.Default.CreateScope())
|
||||
{
|
||||
UserInfo = await scope.ServiceProvider
|
||||
.GetRequiredService<UserClient2>()
|
||||
.GetUserFullInfoAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// 自动填充 Ltoken
|
||||
if (Ltoken == null)
|
||||
{
|
||||
string? ltoken = await scope.ServiceProvider
|
||||
.GetRequiredService<PassportClient2>()
|
||||
.GetLtokenBySTokenAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (ltoken != null)
|
||||
{
|
||||
Cookie ltokenCookie = Cookie.Parse($"ltuid={Entity.Aid};ltoken={ltoken}");
|
||||
Entity.Ltoken = ltokenCookie;
|
||||
}
|
||||
}
|
||||
|
||||
string? actionTicket = await scope.ServiceProvider
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetActionTicketByStokenAsync("game_role", Entity)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
UserGameRoles = await scope.ServiceProvider
|
||||
.GetRequiredService<BindingClient>()
|
||||
.GetUserGameRolesByActionTicketAsync(actionTicket!, Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// 自动填充 CookieToken
|
||||
if (CookieToken == null)
|
||||
{
|
||||
string? cookieToken = await scope.ServiceProvider
|
||||
.GetRequiredService<PassportClient2>()
|
||||
.GetCookieAccountInfoBySTokenAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (cookieToken != null)
|
||||
{
|
||||
Cookie cookieTokenCookie = Cookie.Parse($"account_id={Entity.Aid};cookie_token={cookieToken}");
|
||||
Entity.CookieToken = cookieTokenCookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SelectedUserGameRole = UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
|
||||
|
||||
|
||||
@@ -87,6 +87,21 @@ public class Achievement : IEquatable<Achievement>
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换到UIAF物品
|
||||
/// </summary>
|
||||
/// <returns>UIAF物品</returns>
|
||||
public UIAFItem ToUIAFItem()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = Id,
|
||||
Current = Current,
|
||||
Status = Status,
|
||||
Timestamp = Time.ToUniversalTime().ToUnixTimeSeconds(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Achievement? other)
|
||||
{
|
||||
|
||||
@@ -15,16 +15,16 @@ internal class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<User> builder)
|
||||
{
|
||||
builder.Property(e => e.Cookie)
|
||||
builder.Property(e => e.CookieToken)
|
||||
.HasColumnType("TEXT")
|
||||
.HasConversion(
|
||||
e => e!.ToString(),
|
||||
e => Cookie.Parse(e));
|
||||
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
|
||||
|
||||
builder.Property(e => e.Ltoken)
|
||||
.HasColumnType("TEXT")
|
||||
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
|
||||
|
||||
builder.Property(e => e.Stoken)
|
||||
.HasColumnType("TEXT")
|
||||
.HasConversion(
|
||||
e => e!.ToString(),
|
||||
e => Cookie.Parse(e));
|
||||
.HasConversion(e => e!.ToString(), e => Cookie.Parse(e));
|
||||
}
|
||||
}
|
||||
30
src/Snap.Hutao/Snap.Hutao/Model/Entity/ObjectCacheEntry.cs
Normal file
30
src/Snap.Hutao/Snap.Hutao/Model/Entity/ObjectCacheEntry.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库对象缓存
|
||||
/// </summary>
|
||||
[Table("object_cache")]
|
||||
public class ObjectCacheEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键
|
||||
/// </summary>
|
||||
[Key]
|
||||
public string Key { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 过期时间
|
||||
/// </summary>
|
||||
public DateTimeOffset ExpireTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值字符串
|
||||
/// </summary>
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
@@ -92,4 +92,4 @@ public class SettingEntry
|
||||
/// 值
|
||||
/// </summary>
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,14 @@ public class User : ISelectable
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户的 Cookie
|
||||
/// 用户的 Cookie Token
|
||||
/// </summary>
|
||||
public Cookie? Cookie { get; set; }
|
||||
public Cookie? CookieToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户的 Ltoken
|
||||
/// </summary>
|
||||
public Cookie? Ltoken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户的 Stoken V2
|
||||
@@ -53,6 +58,9 @@ public class User : ISelectable
|
||||
/// <returns>新创建的用户</returns>
|
||||
public static User Create(Cookie cookie)
|
||||
{
|
||||
return new() { Cookie = cookie };
|
||||
_ = cookie.TryGetAsStoken(out Cookie? stoken);
|
||||
_ = cookie.TryGetAsLtoken(out Cookie? ltoken);
|
||||
|
||||
return new() { Stoken = stoken, Ltoken = ltoken };
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,14 @@ namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||
/// </summary>
|
||||
public class UIAF
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前发行的版本
|
||||
/// </summary>
|
||||
public const string CurrentVersion = "v1.1";
|
||||
|
||||
private static readonly List<string> SupportedVersion = new()
|
||||
{
|
||||
"v1.1",
|
||||
CurrentVersion,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Extension;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||
@@ -25,6 +26,7 @@ public class UIAFInfo
|
||||
/// <summary>
|
||||
/// 导出时间
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset ExportDateTime
|
||||
{
|
||||
// Hot fix | 1.0.31 | UIAF.Info.ExportTimestamp can be milliseconds
|
||||
@@ -42,4 +44,20 @@ public class UIAFInfo
|
||||
/// </summary>
|
||||
[JsonPropertyName("uiaf_version")]
|
||||
public string? UIAFVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的专用 UIAF 信息
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <returns>专用 UIAF 信息</returns>
|
||||
public static UIAFInfo Create()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
|
||||
ExportApp = "胡桃",
|
||||
ExportAppVersion = CoreEnvironment.Version.ToString(),
|
||||
UIAFVersion = UIAF.CurrentVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
/// <summary>
|
||||
@@ -14,10 +16,10 @@ public class UIGF
|
||||
/// </summary>
|
||||
public const string CurrentVersion = "v2.2";
|
||||
|
||||
private static readonly List<string> SupportedVersion = new()
|
||||
private static readonly ImmutableList<string> SupportedVersion = new List<string>()
|
||||
{
|
||||
"v2.1", CurrentVersion,
|
||||
};
|
||||
}.ToImmutableList();
|
||||
|
||||
/// <summary>
|
||||
/// 信息
|
||||
@@ -37,6 +39,6 @@ public class UIGF
|
||||
/// <returns>当前UIAF对象是否受支持</returns>
|
||||
public bool IsCurrentVersionSupported()
|
||||
{
|
||||
return SupportedVersion.Contains(Info.UIGFVersion ?? string.Empty);
|
||||
return SupportedVersion.Contains(Info?.UIGFVersion ?? string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -27,4 +27,4 @@ internal class EquipIconConverter : ValueConverterBase<string, Uri>
|
||||
{
|
||||
return IconNameToUri(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 GachaEquipIconConverter : ValueConverterBase<string, Uri>
|
||||
{
|
||||
private const string BaseUrl = "https://static.snapgenshin.com/GachaEquipIcon/UI_Gacha_{0}.png";
|
||||
|
||||
/// <summary>
|
||||
/// 名称转Uri
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns>链接</returns>
|
||||
public static Uri IconNameToUri(string name)
|
||||
{
|
||||
name = name["UI_".Length..];
|
||||
return new Uri(string.Format(BaseUrl, name));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Uri Convert(string from)
|
||||
{
|
||||
return IconNameToUri(from);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Reliquary;
|
||||
|
||||
/// <summary>
|
||||
@@ -8,6 +10,11 @@ namespace Snap.Hutao.Model.Metadata.Reliquary;
|
||||
/// </summary>
|
||||
public class ReliquaryAffix : ReliquaryAffixBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public new ReliquaryAffixId Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Reliquary;
|
||||
|
||||
@@ -13,7 +14,7 @@ public class ReliquaryAffixBase
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
public ReliquaryMainAffixId Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 战斗属性
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Reliquary;
|
||||
|
||||
/// <summary>
|
||||
@@ -16,7 +18,7 @@ public class ReliquarySet
|
||||
/// <summary>
|
||||
/// 装备被动Id
|
||||
/// </summary>
|
||||
public int EquipAffixId { get; set; }
|
||||
public EquipAffixId EquipAffixId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 套装名称
|
||||
|
||||
@@ -17,5 +17,5 @@ public class AffixInfo
|
||||
/// 各个等级的描述
|
||||
/// 0-4
|
||||
/// </summary>
|
||||
public List<LevelDescription<int>> Descriptions { get; set; } = default!;
|
||||
public List<LevelDescription> Descriptions { get; set; } = default!;
|
||||
}
|
||||
@@ -7,12 +7,18 @@ namespace Snap.Hutao.Model.Metadata.Weapon;
|
||||
/// 等级与描述
|
||||
/// </summary>
|
||||
/// <typeparam name="TLevel">等级的类型</typeparam>
|
||||
public class LevelDescription<TLevel>
|
||||
public class LevelDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
public TLevel Level { get; set; } = default!;
|
||||
public int Level { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的等级
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string LevelFormatted { get => $"精炼 {Level + 1} 阶"; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
|
||||
@@ -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;
|
||||
@@ -60,6 +61,12 @@ public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality
|
||||
/// </summary>
|
||||
public AffixInfo? Affix { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// [非元数据] 搭配数据
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ComplexWeaponCollocation? Collocation { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public ItemQuality Quality
|
||||
|
||||
@@ -6,9 +6,9 @@ using Snap.Hutao.Model.Primitive.Converter;
|
||||
namespace Snap.Hutao.Model.Primitive;
|
||||
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// 8位 角色Id
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(AvatarIdConverter))]
|
||||
[JsonConverter(typeof(IdentityConverter<AvatarId>))]
|
||||
public readonly struct AvatarId : IEquatable<AvatarId>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Convert;
|
||||
|
||||
namespace Snap.Hutao.Model.Primitive.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// Id 转换器
|
||||
/// </summary>
|
||||
/// <typeparam name="TWrapper">包装类型</typeparam>
|
||||
internal class IdentityConverter<TWrapper> : JsonConverter<TWrapper>
|
||||
where TWrapper : struct
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override TWrapper Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return CastTo<TWrapper>.From(reader.GetInt32());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, TWrapper value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteNumberValue(CastTo<int>.From(value));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
65
src/Snap.Hutao/Snap.Hutao/Model/Primitive/EquipAffixId.cs
Normal file
65
src/Snap.Hutao/Snap.Hutao/Model/Primitive/EquipAffixId.cs
Normal 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>
|
||||
/// 6位 装备属性Id
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(IdentityConverter<EquipAffixId>))]
|
||||
public readonly struct EquipAffixId : IEquatable<EquipAffixId>
|
||||
{
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public readonly int Value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EquipAffixId"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="value">value</param>
|
||||
public EquipAffixId(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static implicit operator int(EquipAffixId value)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public static implicit operator EquipAffixId(int value)
|
||||
{
|
||||
return new(value);
|
||||
}
|
||||
|
||||
public static bool operator ==(EquipAffixId left, EquipAffixId right)
|
||||
{
|
||||
return left.Value == right.Value;
|
||||
}
|
||||
|
||||
public static bool operator !=(EquipAffixId left, EquipAffixId right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(EquipAffixId other)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is EquipAffixId other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
/// 7位 装备属性Id
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(IdentityConverter<ExtendedEquipAffixId>))]
|
||||
public readonly struct ExtendedEquipAffixId : IEquatable<ExtendedEquipAffixId>
|
||||
{
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public readonly int Value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExtendedEquipAffixId"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="value">value</param>
|
||||
public ExtendedEquipAffixId(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static implicit operator int(ExtendedEquipAffixId value)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public static implicit operator ExtendedEquipAffixId(int value)
|
||||
{
|
||||
return new(value);
|
||||
}
|
||||
|
||||
public static bool operator ==(ExtendedEquipAffixId left, ExtendedEquipAffixId right)
|
||||
{
|
||||
return left.Value == right.Value;
|
||||
}
|
||||
|
||||
public static bool operator !=(ExtendedEquipAffixId left, ExtendedEquipAffixId right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(ExtendedEquipAffixId other)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ExtendedEquipAffixId other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
/// 6位 圣遗物副词条Id
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(IdentityConverter<ReliquaryAffixId>))]
|
||||
public readonly struct ReliquaryAffixId : IEquatable<ReliquaryAffixId>
|
||||
{
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public readonly int Value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReliquaryAffixId"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="value">value</param>
|
||||
public ReliquaryAffixId(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static implicit operator int(ReliquaryAffixId value)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public static implicit operator ReliquaryAffixId(int value)
|
||||
{
|
||||
return new(value);
|
||||
}
|
||||
|
||||
public static bool operator ==(ReliquaryAffixId left, ReliquaryAffixId right)
|
||||
{
|
||||
return left.Value == right.Value;
|
||||
}
|
||||
|
||||
public static bool operator !=(ReliquaryAffixId left, ReliquaryAffixId right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(ReliquaryAffixId other)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ReliquaryAffixId other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
/// 5位 圣遗物主属性Id
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(IdentityConverter<ReliquaryMainAffixId>))]
|
||||
public readonly struct ReliquaryMainAffixId : IEquatable<ReliquaryMainAffixId>
|
||||
{
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public readonly int Value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReliquaryMainAffixId"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="value">value</param>
|
||||
public ReliquaryMainAffixId(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static implicit operator int(ReliquaryMainAffixId value)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public static implicit operator ReliquaryMainAffixId(int value)
|
||||
{
|
||||
return new(value);
|
||||
}
|
||||
|
||||
public static bool operator ==(ReliquaryMainAffixId left, ReliquaryMainAffixId right)
|
||||
{
|
||||
return left.Value == right.Value;
|
||||
}
|
||||
|
||||
public static bool operator !=(ReliquaryMainAffixId left, ReliquaryMainAffixId right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(ReliquaryMainAffixId other)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ReliquaryMainAffixId other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ using Snap.Hutao.Model.Primitive.Converter;
|
||||
namespace Snap.Hutao.Model.Primitive;
|
||||
|
||||
/// <summary>
|
||||
/// 武器Id
|
||||
/// 5位 武器Id
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(WeaponIdConverter))]
|
||||
[JsonConverter(typeof(IdentityConverter<WeaponId>))]
|
||||
public readonly struct WeaponId : IEquatable<WeaponId>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -4,6 +4,7 @@ WM_NCRBUTTONDOWN
|
||||
WM_NCRBUTTONUP
|
||||
|
||||
// Type definition
|
||||
HRESULT
|
||||
MINMAXINFO
|
||||
|
||||
// Comctl32
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.2.0.0" />
|
||||
Version="1.2.6.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.Runtime.InteropServices;
|
||||
using WinRT;
|
||||
@@ -43,6 +44,8 @@ public static partial class Program
|
||||
Application.Start(InitializeApp);
|
||||
ServiceScopeExtension.DisposeLast();
|
||||
}
|
||||
|
||||
AppInstance.GetCurrent().UnregisterKey();
|
||||
}
|
||||
|
||||
private static void InitializeApp(ApplicationInitializationCallbackParams param)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 479 KiB |
@@ -88,7 +88,7 @@ public interface IInfoBarService
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="delay">关闭延迟</param>
|
||||
void Warning(string message, int delay = 0);
|
||||
void Warning(string message, int delay = 30000);
|
||||
|
||||
/// <summary>
|
||||
/// 显示警告信息
|
||||
@@ -96,5 +96,5 @@ public interface IInfoBarService
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="delay">关闭延迟</param>
|
||||
void Warning(string title, string message, int delay = 0);
|
||||
void Warning(string title, string message, int delay = 30000);
|
||||
}
|
||||
|
||||
@@ -120,6 +120,24 @@ internal class AchievementService : IAchievementService
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<UIAF> ExportToUIAFAsync(EntityArchive archive)
|
||||
{
|
||||
List<UIAFItem> list = appDbContext.Achievements
|
||||
.Where(i => i.ArchiveId == archive.InnerId)
|
||||
.AsEnumerable()
|
||||
.Select(i => i.ToUIAFItem())
|
||||
.ToList();
|
||||
|
||||
UIAF uigf = new()
|
||||
{
|
||||
Info = UIAFInfo.Create(),
|
||||
List = list,
|
||||
};
|
||||
|
||||
return Task.FromResult(uigf);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImportResult ImportFromUIAF(EntityArchive archive, List<UIAFItem> list, ImportStrategy strategy)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,13 @@ internal interface IAchievementService
|
||||
/// </summary>
|
||||
EntityArchive? CurrentArchive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异步导出到UIAF
|
||||
/// </summary>
|
||||
/// <param name="selectedArchive">存档</param>
|
||||
/// <returns>UIAF</returns>
|
||||
Task<UIAF> ExportToUIAFAsync(EntityArchive selectedArchive);
|
||||
|
||||
/// <summary>
|
||||
/// 获取整合的成就
|
||||
/// </summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
|
||||
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
|
||||
@@ -22,10 +23,10 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
/// </summary>
|
||||
internal class SummaryAvatarFactory
|
||||
{
|
||||
private readonly Dictionary<int, MetadataAvatar> idAvatarMap;
|
||||
private readonly Dictionary<int, FightProperty> idRelicMainPropMap;
|
||||
private readonly Dictionary<int, ReliquaryAffix> idReliquaryAffixMap;
|
||||
private readonly Dictionary<int, MetadataWeapon> idWeaponMap;
|
||||
private readonly Dictionary<AvatarId, MetadataAvatar> idAvatarMap;
|
||||
private readonly Dictionary<WeaponId, MetadataWeapon> idWeaponMap;
|
||||
private readonly Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap;
|
||||
private readonly Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap;
|
||||
private readonly List<ReliquaryLevel> reliqueryLevels;
|
||||
private readonly List<MetadataReliquary> reliquaries;
|
||||
|
||||
@@ -42,18 +43,18 @@ internal class SummaryAvatarFactory
|
||||
/// <param name="reliquaries">圣遗物</param>
|
||||
/// <param name="avatarInfo">角色信息</param>
|
||||
public SummaryAvatarFactory(
|
||||
Dictionary<int, MetadataAvatar> idAvatarMap,
|
||||
Dictionary<int, MetadataWeapon> idWeaponMap,
|
||||
Dictionary<int, FightProperty> idRelicMainPropMap,
|
||||
Dictionary<int, ReliquaryAffix> idReliquaryAffixMap,
|
||||
Dictionary<AvatarId, MetadataAvatar> idAvatarMap,
|
||||
Dictionary<WeaponId, MetadataWeapon> idWeaponMap,
|
||||
Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap,
|
||||
Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap,
|
||||
List<ReliquaryLevel> reliqueryLevels,
|
||||
List<MetadataReliquary> reliquaries,
|
||||
ModelAvatarInfo avatarInfo)
|
||||
{
|
||||
this.idAvatarMap = idAvatarMap;
|
||||
this.idWeaponMap = idWeaponMap;
|
||||
this.idRelicMainPropMap = idRelicMainPropMap;
|
||||
this.idReliquaryAffixMap = idReliquaryAffixMap;
|
||||
this.idWeaponMap = idWeaponMap;
|
||||
this.reliqueryLevels = reliqueryLevels;
|
||||
this.reliquaries = reliquaries;
|
||||
this.avatarInfo = avatarInfo;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
|
||||
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
|
||||
@@ -33,10 +34,10 @@ internal class SummaryFactory : ISummaryFactory
|
||||
/// <inheritdoc/>
|
||||
public async Task<Summary> CreateAsync(ModelPlayerInfo playerInfo, IEnumerable<ModelAvatarInfo> avatarInfos, CancellationToken token)
|
||||
{
|
||||
Dictionary<int, MetadataAvatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
|
||||
Dictionary<int, MetadataWeapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
|
||||
Dictionary<int, FightProperty> idRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false);
|
||||
Dictionary<int, ReliquaryAffix> idReliquaryAffixMap = await metadataService.GetIdReliquaryAffixMapAsync(token).ConfigureAwait(false);
|
||||
Dictionary<AvatarId, MetadataAvatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
|
||||
Dictionary<WeaponId, MetadataWeapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
|
||||
Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false);
|
||||
Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap = await metadataService.GetIdReliquaryAffixMapAsync(token).ConfigureAwait(false);
|
||||
|
||||
List<ReliquaryLevel> reliqueryLevels = await metadataService.GetReliquaryLevelsAsync(token).ConfigureAwait(false);
|
||||
List<MetadataReliquary> reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
|
||||
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
|
||||
using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon;
|
||||
@@ -18,10 +19,10 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
/// </summary>
|
||||
internal class SummaryFactoryImplementation
|
||||
{
|
||||
private readonly Dictionary<int, MetadataAvatar> idAvatarMap;
|
||||
private readonly Dictionary<int, FightProperty> idRelicMainPropMap;
|
||||
private readonly Dictionary<int, MetadataWeapon> idWeaponMap;
|
||||
private readonly Dictionary<int, ReliquaryAffix> idReliquaryAffixMap;
|
||||
private readonly Dictionary<AvatarId, MetadataAvatar> idAvatarMap;
|
||||
private readonly Dictionary<WeaponId, MetadataWeapon> idWeaponMap;
|
||||
private readonly Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap;
|
||||
private readonly Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap;
|
||||
private readonly List<ReliquaryLevel> reliqueryLevels;
|
||||
private readonly List<MetadataReliquary> reliquaries;
|
||||
|
||||
@@ -35,10 +36,10 @@ internal class SummaryFactoryImplementation
|
||||
/// <param name="reliqueryLevels">圣遗物主属性等级</param>
|
||||
/// <param name="reliquaries">圣遗物</param>
|
||||
public SummaryFactoryImplementation(
|
||||
Dictionary<int, MetadataAvatar> idAvatarMap,
|
||||
Dictionary<int, MetadataWeapon> idWeaponMap,
|
||||
Dictionary<int, FightProperty> idRelicMainPropMap,
|
||||
Dictionary<int, ReliquaryAffix> idReliquaryAffixMap,
|
||||
Dictionary<AvatarId, MetadataAvatar> idAvatarMap,
|
||||
Dictionary<WeaponId, MetadataWeapon> idWeaponMap,
|
||||
Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap,
|
||||
Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap,
|
||||
List<ReliquaryLevel> reliqueryLevels,
|
||||
List<MetadataReliquary> reliquaries)
|
||||
{
|
||||
|
||||
@@ -118,7 +118,7 @@ internal static class SummaryHelper
|
||||
double def = fightPropMap.GetValueOrDefault2(FightProperty.FIGHT_PROP_DEFENSE); // 8
|
||||
double defPercent = fightPropMap.GetValueOrDefault2(FightProperty.FIGHT_PROP_DEFENSE_PERCENT); // 9
|
||||
double defAdd = def + (baseDef * defPercent);
|
||||
double maxDef = baseDef + defPercent;
|
||||
double maxDef = baseDef + defAdd;
|
||||
Pair2<string, string, string?> defPair2 = PropertyInfoDescriptor.FormatIntegerPair2("防御力", FormatMethod.Integer, maxDef, defAdd);
|
||||
|
||||
double em = fightPropMap.GetValueOrDefault2(FightProperty.FIGHT_PROP_ELEMENT_MASTERY); // 28
|
||||
|
||||
@@ -6,6 +6,7 @@ using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using System.Runtime.InteropServices;
|
||||
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
|
||||
@@ -20,8 +21,8 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
/// </summary>
|
||||
internal class SummaryReliquaryFactory
|
||||
{
|
||||
private readonly Dictionary<int, MetadataReliquaryAffix> idReliquaryAffixMap;
|
||||
private readonly Dictionary<int, FightProperty> idRelicMainPropMap;
|
||||
private readonly Dictionary<ReliquaryAffixId, MetadataReliquaryAffix> idReliquaryAffixMap;
|
||||
private readonly Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap;
|
||||
private readonly List<ReliquaryLevel> reliqueryLevels;
|
||||
private readonly List<MetadataReliquary> reliquaries;
|
||||
|
||||
@@ -38,8 +39,8 @@ internal class SummaryReliquaryFactory
|
||||
/// <param name="avatarInfo">角色信息</param>
|
||||
/// <param name="equip">圣遗物</param>
|
||||
public SummaryReliquaryFactory(
|
||||
Dictionary<int, MetadataReliquaryAffix> idReliquaryAffixMap,
|
||||
Dictionary<int, FightProperty> idRelicMainPropMap,
|
||||
Dictionary<ReliquaryAffixId, MetadataReliquaryAffix> idReliquaryAffixMap,
|
||||
Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap,
|
||||
List<ReliquaryLevel> reliqueryLevels,
|
||||
List<MetadataReliquary> reliquaries,
|
||||
ModelAvatarInfo avatarInfo,
|
||||
@@ -61,7 +62,7 @@ internal class SummaryReliquaryFactory
|
||||
public PropertyReliquary CreateReliquary()
|
||||
{
|
||||
MetadataReliquary reliquary = reliquaries.Single(r => r.Ids.Contains(equip.ItemId));
|
||||
List<ReliquarySubProperty> subProperty = equip.Reliquary!.AppendPropIdList.Select(id => CreateSubProperty(id)).ToList();
|
||||
List<ReliquarySubProperty> subProperty = equip.Reliquary!.AppendPropIdList.EmptyIfNull().Select(CreateSubProperty).ToList();
|
||||
|
||||
int affixCount = GetAffixCount(reliquary);
|
||||
Span<ReliquarySubProperty> span = CollectionsMarshal.AsSpan(subProperty);
|
||||
|
||||
@@ -108,9 +108,8 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
|
||||
entry.DailyNote = dailyNote;
|
||||
|
||||
// cache
|
||||
Guid userId = entry.UserId;
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
entries?.Single(e => e.UserId == userId).UpdateDailyNote(dailyNote);
|
||||
entries?.SingleOrDefault(e => e.UserId == entry.UserId && e.Uid == entry.Uid)?.UpdateDailyNote(dailyNote);
|
||||
|
||||
if (notify)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ 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.Model.Primitive;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog.Factory;
|
||||
@@ -37,8 +38,8 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
/// <inheritdoc/>
|
||||
public async Task<GachaStatistics> CreateAsync(IEnumerable<GachaItem> items)
|
||||
{
|
||||
Dictionary<int, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
Dictionary<WeaponId, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
|
||||
Dictionary<string, Avatar> nameAvatarMap = await metadataService.GetNameToAvatarMapAsync().ConfigureAwait(false);
|
||||
Dictionary<string, Weapon> nameWeaponMap = await metadataService.GetNameToWeaponMapAsync().ConfigureAwait(false);
|
||||
@@ -66,8 +67,8 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
||||
private static GachaStatistics CreateCore(
|
||||
IOrderedEnumerable<GachaItem> items,
|
||||
List<HistoryWishBuilder> historyWishBuilders,
|
||||
Dictionary<int, Avatar> avatarMap,
|
||||
Dictionary<int, Weapon> weaponMap,
|
||||
Dictionary<AvatarId, Avatar> avatarMap,
|
||||
Dictionary<WeaponId, Weapon> weaponMap,
|
||||
bool isEmptyHistoryWishVisible)
|
||||
{
|
||||
TypedWishSummaryBuilder permanentWishBuilder = new("奔行世间", TypedWishSummaryBuilder.PermanentWish, 90, 10);
|
||||
|
||||
@@ -13,6 +13,7 @@ using Snap.Hutao.Model.Binding.Gacha.Abstraction;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
using Snap.Hutao.Model.Metadata.Abstraction;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Service.GachaLog.Factory;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
@@ -52,8 +53,8 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
|
||||
private Dictionary<string, Model.Metadata.Avatar.Avatar>? nameAvatarMap;
|
||||
private Dictionary<string, Model.Metadata.Weapon.Weapon>? nameWeaponMap;
|
||||
|
||||
private Dictionary<int, Model.Metadata.Avatar.Avatar>? idAvatarMap;
|
||||
private Dictionary<int, Model.Metadata.Weapon.Weapon>? idWeaponMap;
|
||||
private Dictionary<AvatarId, Model.Metadata.Avatar.Avatar>? idAvatarMap;
|
||||
private Dictionary<WeaponId, Model.Metadata.Weapon.Weapon>? idWeaponMap;
|
||||
private ObservableCollection<GachaArchive>? archiveCollection;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -37,7 +37,7 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
|
||||
Model.Binding.User.User? user = userService.Current;
|
||||
if (user != null && user.SelectedUserGameRole != null)
|
||||
{
|
||||
if (user.HasStoken)
|
||||
if (user.Stoken != null)
|
||||
{
|
||||
PlayerUid uid = (PlayerUid)user.SelectedUserGameRole;
|
||||
GenAuthKeyData data = GenAuthKeyData.CreateForWebViewGacha(uid);
|
||||
|
||||
@@ -28,6 +28,17 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
|
||||
/// <inheritdoc/>
|
||||
public string Name { get => nameof(GachaLogUrlWebCacheProvider); }
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存文件路径
|
||||
/// </summary>
|
||||
/// <param name="path">游戏路径</param>
|
||||
/// <returns>缓存文件路径</returns>
|
||||
public static string GetCacheFile(string path)
|
||||
{
|
||||
string folder = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
return Path.Combine(folder, @"YuanShen_Data\webCaches\Cache\Cache_Data\data_2");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, string>> GetQueryAsync()
|
||||
{
|
||||
@@ -35,8 +46,7 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
string folder = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
string cacheFile = Path.Combine(folder, @"YuanShen_Data\webCaches\Cache\Cache_Data\data_2");
|
||||
string cacheFile = GetCacheFile(path);
|
||||
|
||||
TemporaryFile tempFile;
|
||||
try
|
||||
|
||||
@@ -22,5 +22,6 @@ internal interface IGameLocator : INamed
|
||||
/// 路径应当包含启动器文件名称
|
||||
/// </summary>
|
||||
/// <returns>游戏启动器位置</returns>
|
||||
[Obsolete("不应定位启动器位置")]
|
||||
Task<ValueResult<bool, string>> LocateLauncherPathAsync();
|
||||
}
|
||||
@@ -41,12 +41,7 @@ internal class ManualGameLocator : IGameLocator
|
||||
|
||||
private async Task<ValueResult<bool, string>> LocateInternalAsync(string fileName)
|
||||
{
|
||||
FileOpenPicker picker = pickerFactory.GetFileOpenPicker();
|
||||
picker.FileTypeFilter.Add(".exe");
|
||||
picker.SuggestedStartLocation = PickerLocationId.Desktop;
|
||||
|
||||
// System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.
|
||||
// Not sure what's going on here.
|
||||
FileOpenPicker picker = pickerFactory.GetFileOpenPicker(PickerLocationId.Desktop, "选择游戏本体", ".exe");
|
||||
if (await picker.PickSingleFileAsync() is StorageFile file)
|
||||
{
|
||||
string path = file.Path;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Snap.Hutao.Model.Binding.Hutao;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
@@ -16,20 +18,24 @@ namespace Snap.Hutao.Service.Hutao;
|
||||
[Injection(InjectAs.Singleton, typeof(IHutaoCache))]
|
||||
internal class HutaoCache : IHutaoCache
|
||||
{
|
||||
private readonly IHutaoService hutaoService;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IServiceScopeFactory scopeFactory;
|
||||
|
||||
private Dictionary<int, Avatar>? idAvatarExtendedMap;
|
||||
private Dictionary<AvatarId, Avatar>? idAvatarExtendedMap;
|
||||
|
||||
private bool isDatabaseViewModelInitialized;
|
||||
private bool isWikiAvatarViewModelInitiaized;
|
||||
private bool isWikiWeaponViewModelInitiaized;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃 API 缓存
|
||||
/// </summary>
|
||||
/// <param name="hutaoService">胡桃服务</param>
|
||||
/// <param name="metadataService">元数据服务</param>
|
||||
public HutaoCache(IHutaoService hutaoService, IMetadataService metadataService)
|
||||
/// <param name="scopeFactory">范围工厂</param>
|
||||
public HutaoCache(IMetadataService metadataService, IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
this.hutaoService = hutaoService;
|
||||
this.metadataService = metadataService;
|
||||
this.scopeFactory = scopeFactory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -50,12 +56,20 @@ internal class HutaoCache : IHutaoCache
|
||||
/// <inheritdoc/>
|
||||
public List<ComplexAvatarCollocation>? AvatarCollocations { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<ComplexWeaponCollocation>? WeaponCollocations { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForDatabaseViewModelAsync()
|
||||
{
|
||||
if (isDatabaseViewModelInitialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
Dictionary<int, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
|
||||
Dictionary<AvatarId, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
|
||||
|
||||
Task avatarAppearanceRankTask = AvatarAppearanceRankAsync(idAvatarMap);
|
||||
Task avatarUsageRank = AvatarUsageRanksAsync(idAvatarMap);
|
||||
@@ -71,6 +85,7 @@ internal class HutaoCache : IHutaoCache
|
||||
ovewviewTask)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
isDatabaseViewModelInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -80,36 +95,76 @@ internal class HutaoCache : IHutaoCache
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForWikiAvatarViewModelAsync()
|
||||
{
|
||||
if (isWikiAvatarViewModelInitiaized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
Dictionary<int, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
|
||||
Dictionary<int, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, Model.Metadata.Reliquary.ReliquarySet> idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false);
|
||||
Dictionary<AvatarId, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
|
||||
Dictionary<WeaponId, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
Dictionary<EquipAffixId, Model.Metadata.Reliquary.ReliquarySet> idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false);
|
||||
|
||||
// AvatarCollocation
|
||||
List<AvatarCollocation> avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false);
|
||||
AvatarCollocations = avatarCollocationsRaw.Select(co =>
|
||||
List<AvatarCollocation> avatarCollocationsRaw;
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
return new ComplexAvatarCollocation(idAvatarMap[co.AvatarId])
|
||||
{
|
||||
AvatarId = co.AvatarId,
|
||||
Avatars = co.Avatars.Select(a => new ComplexAvatar(idAvatarMap[a.Item], a.Rate)).ToList(),
|
||||
Weapons = co.Weapons.Select(w => new ComplexWeapon(idWeaponMap[w.Item], w.Rate)).ToList(),
|
||||
ReliquarySets = co.Reliquaries.Select(r => new ComplexReliquarySet(r, idReliquarySetMap)).ToList(),
|
||||
};
|
||||
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
|
||||
avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
AvatarCollocations = avatarCollocationsRaw.Select(co => new ComplexAvatarCollocation()
|
||||
{
|
||||
AvatarId = co.AvatarId,
|
||||
Avatars = co.Avatars.Select(a => new ComplexAvatar(idAvatarMap[a.Item], a.Rate)).ToList(),
|
||||
Weapons = co.Weapons.Select(w => new ComplexWeapon(idWeaponMap[w.Item], w.Rate)).ToList(),
|
||||
ReliquarySets = co.Reliquaries.Select(r => new ComplexReliquarySet(r, idReliquarySetMap)).ToList(),
|
||||
}).ToList();
|
||||
|
||||
isWikiAvatarViewModelInitiaized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<int, Avatar>> GetIdAvatarMapExtendedAsync()
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeForWikiWeaponViewModelAsync()
|
||||
{
|
||||
if (isWikiWeaponViewModelInitiaized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
Dictionary<AvatarId, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
|
||||
|
||||
List<WeaponCollocation> weaponCollocationsRaw;
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
|
||||
weaponCollocationsRaw = await hutaoService.GetWeaponCollocationsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
WeaponCollocations = weaponCollocationsRaw.Select(co => new ComplexWeaponCollocation()
|
||||
{
|
||||
WeaponId = co.WeaponId,
|
||||
Avatars = co.Avatars.Select(a => new ComplexAvatar(idAvatarMap[a.Item], a.Rate)).ToList(),
|
||||
}).ToList();
|
||||
|
||||
isWikiWeaponViewModelInitiaized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<AvatarId, Avatar>> GetIdAvatarMapExtendedAsync()
|
||||
{
|
||||
if (idAvatarExtendedMap == null)
|
||||
{
|
||||
Dictionary<int, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
idAvatarExtendedMap = new(idAvatarMap)
|
||||
{
|
||||
[AvatarIds.PlayerBoy] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerBoy", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE },
|
||||
@@ -120,9 +175,15 @@ internal class HutaoCache : IHutaoCache
|
||||
return idAvatarExtendedMap;
|
||||
}
|
||||
|
||||
private async Task AvatarAppearanceRankAsync(Dictionary<int, Avatar> idAvatarMap)
|
||||
private async Task AvatarAppearanceRankAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
|
||||
{
|
||||
List<AvatarAppearanceRank> avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false);
|
||||
List<AvatarAppearanceRank> avatarAppearanceRanksRaw;
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
|
||||
avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
AvatarAppearanceRanks = avatarAppearanceRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank
|
||||
{
|
||||
Floor = $"第 {rank.Floor} 层",
|
||||
@@ -130,9 +191,15 @@ internal class HutaoCache : IHutaoCache
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private async Task AvatarUsageRanksAsync(Dictionary<int, Avatar> idAvatarMap)
|
||||
private async Task AvatarUsageRanksAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
|
||||
{
|
||||
List<AvatarUsageRank> avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false);
|
||||
List<AvatarUsageRank> avatarUsageRanksRaw;
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
|
||||
avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
AvatarUsageRanks = avatarUsageRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new ComplexAvatarRank
|
||||
{
|
||||
Floor = $"第 {rank.Floor} 层",
|
||||
@@ -140,23 +207,39 @@ internal class HutaoCache : IHutaoCache
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private async Task AvatarConstellationInfosAsync(Dictionary<int, Avatar> idAvatarMap)
|
||||
private async Task AvatarConstellationInfosAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
|
||||
{
|
||||
List<AvatarConstellationInfo> avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false);
|
||||
List<AvatarConstellationInfo> avatarConstellationInfosRaw;
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
|
||||
avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
AvatarConstellationInfos = avatarConstellationInfosRaw.OrderBy(i => i.HoldingRate).Select(info =>
|
||||
{
|
||||
return new ComplexAvatarConstellationInfo(idAvatarMap[info.AvatarId], info.HoldingRate, info.Constellations.Select(x => x.Rate));
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private async Task TeamAppearancesAsync(Dictionary<int, Avatar> idAvatarMap)
|
||||
private async Task TeamAppearancesAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
|
||||
{
|
||||
List<TeamAppearance> teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false);
|
||||
List<TeamAppearance> teamAppearancesRaw;
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
|
||||
teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
TeamAppearances = teamAppearancesRaw.OrderByDescending(t => t.Floor).Select(team => new ComplexTeamRank(team, idAvatarMap)).ToList();
|
||||
}
|
||||
|
||||
private async Task OverviewAsync()
|
||||
{
|
||||
Overview = await hutaoService.GetOverviewAsync().ConfigureAwait(false);
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
|
||||
Overview = await hutaoService.GetOverviewAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
@@ -10,21 +13,27 @@ namespace Snap.Hutao.Service.Hutao;
|
||||
/// <summary>
|
||||
/// 胡桃 API 服务
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient, typeof(IHutaoService))]
|
||||
[Injection(InjectAs.Scoped, typeof(IHutaoService))]
|
||||
internal class HutaoService : IHutaoService
|
||||
{
|
||||
private readonly HomaClient homaClient;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly JsonSerializerOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃 API 服务
|
||||
/// </summary>
|
||||
/// <param name="homaClient">胡桃 API 客户端</param>
|
||||
/// <param name="memoryCache">内存缓存</param>
|
||||
public HutaoService(HomaClient homaClient, IMemoryCache memoryCache)
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
/// <param name="options">Json序列化选项</param>
|
||||
public HutaoService(HomaClient homaClient, IMemoryCache memoryCache, AppDbContext appDbContext, JsonSerializerOptions options)
|
||||
{
|
||||
this.homaClient = homaClient;
|
||||
this.memoryCache = memoryCache;
|
||||
this.appDbContext = appDbContext;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -57,6 +66,12 @@ internal class HutaoService : IHutaoService
|
||||
return FromCacheOrWebAsync(nameof(AvatarCollocation), homaClient.GetAvatarCollocationsAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<WeaponCollocation>> GetWeaponCollocationsAsync()
|
||||
{
|
||||
return FromCacheOrWebAsync(nameof(WeaponCollocation), homaClient.GetWeaponCollocationsAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<TeamAppearance>> GetTeamAppearancesAsync()
|
||||
{
|
||||
@@ -71,7 +86,27 @@ internal class HutaoService : IHutaoService
|
||||
return (T)cache!;
|
||||
}
|
||||
|
||||
if (appDbContext.ObjectCache.SingleOrDefault(e => e.Key == key) is ObjectCacheEntry entry)
|
||||
{
|
||||
if (entry.ExpireTime > DateTimeOffset.Now)
|
||||
{
|
||||
T value = JsonSerializer.Deserialize<T>(entry.Value!, options)!;
|
||||
return memoryCache.Set(key, value, TimeSpan.FromMinutes(30));
|
||||
}
|
||||
else
|
||||
{
|
||||
appDbContext.ObjectCache.RemoveAndSave(entry);
|
||||
}
|
||||
}
|
||||
|
||||
T web = await taskFunc(default).ConfigureAwait(false);
|
||||
appDbContext.ObjectCache.AddAndSave(new()
|
||||
{
|
||||
Key = key,
|
||||
ExpireTime = DateTimeOffset.Now.AddHours(4),
|
||||
Value = JsonSerializer.Serialize(web, options),
|
||||
});
|
||||
|
||||
return memoryCache.Set(key, web, TimeSpan.FromMinutes(30));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,15 +41,26 @@ internal interface IHutaoCache
|
||||
/// </summary>
|
||||
List<ComplexAvatarCollocation>? AvatarCollocations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 武器搭配
|
||||
/// </summary>
|
||||
List<ComplexWeaponCollocation>? WeaponCollocations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 为数据库视图模型初始化
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
/// <returns>是否初始化完成</returns>
|
||||
ValueTask<bool> InitializeForDatabaseViewModelAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 为Wiki角色视图模型初始化
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
/// <returns>是否初始化完成</returns>
|
||||
ValueTask<bool> InitializeForWikiAvatarViewModelAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 为Wiki武器视图模型初始化
|
||||
/// </summary>
|
||||
/// <returns>是否初始化完成</returns>
|
||||
ValueTask<bool> InitializeForWikiWeaponViewModelAsync();
|
||||
}
|
||||
@@ -45,4 +45,10 @@ internal interface IHutaoService
|
||||
/// </summary>
|
||||
/// <returns>队伍上场</returns>
|
||||
ValueTask<List<TeamAppearance>> GetTeamAppearancesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取武器搭配
|
||||
/// </summary>
|
||||
/// <returns>武器搭配</returns>
|
||||
ValueTask<List<WeaponCollocation>> GetWeaponCollocationsAsync();
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
@@ -55,13 +56,13 @@ internal class InfoBarService : IInfoBarService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Warning(string message, int delay = 0)
|
||||
public void Warning(string message, int delay = 30000)
|
||||
{
|
||||
PrepareInfoBarAndShow(InfoBarSeverity.Warning, null, message, delay);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Warning(string title, string message, int delay = 0)
|
||||
public void Warning(string title, string message, int delay = 30000)
|
||||
{
|
||||
PrepareInfoBarAndShow(InfoBarSeverity.Warning, title, message, delay);
|
||||
}
|
||||
@@ -117,6 +118,7 @@ internal class InfoBarService : IInfoBarService
|
||||
Title = title,
|
||||
Message = message,
|
||||
IsOpen = true,
|
||||
Transitions = new() { new AddDeleteThemeTransition() },
|
||||
};
|
||||
|
||||
infoBar.Closed += OnInfoBarClosed;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Snap.Hutao.Model.Metadata.Achievement;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata;
|
||||
|
||||
@@ -47,7 +48,7 @@ internal interface IMetadataService
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>装备被动Id到圣遗物套装的映射</returns>
|
||||
ValueTask<Dictionary<int, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default);
|
||||
ValueTask<Dictionary<EquipAffixId, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取卡池配置列表
|
||||
@@ -61,28 +62,28 @@ internal interface IMetadataService
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>Id到角色的字典</returns>
|
||||
ValueTask<Dictionary<int, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default);
|
||||
ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取ID到圣遗物副词条的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>字典</returns>
|
||||
ValueTask<Dictionary<int, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default);
|
||||
ValueTask<Dictionary<ReliquaryAffixId, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物主词条Id与属性的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>字典</returns>
|
||||
ValueTask<Dictionary<int, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default);
|
||||
ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取ID到武器的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>Id到武器的字典</returns>
|
||||
ValueTask<Dictionary<int, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default);
|
||||
ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取名称到角色的字典
|
||||
|
||||
@@ -5,6 +5,7 @@ using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata;
|
||||
|
||||
@@ -14,33 +15,33 @@ namespace Snap.Hutao.Service.Metadata;
|
||||
internal partial class MetadataService
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default)
|
||||
public ValueTask<Dictionary<EquipAffixId, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, ReliquarySet>("ReliquarySet", r => r.EquipAffixId, token);
|
||||
return FromCacheAsDictionaryAsync<EquipAffixId, ReliquarySet>("ReliquarySet", r => r.EquipAffixId, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default)
|
||||
public ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, Avatar>("Avatar", a => a.Id, token);
|
||||
return FromCacheAsDictionaryAsync<AvatarId, Avatar>("Avatar", a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default)
|
||||
public ValueTask<Dictionary<ReliquaryAffixId, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, ReliquaryAffix>("ReliquaryAffix", a => a.Id, token);
|
||||
return FromCacheAsDictionaryAsync<ReliquaryAffixId, ReliquaryAffix>("ReliquaryAffix", a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default)
|
||||
public ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, FightProperty, ReliquaryAffixBase>("ReliquaryMainAffix", r => r.Id, r => r.Type, token);
|
||||
return FromCacheAsDictionaryAsync<ReliquaryMainAffixId, FightProperty, ReliquaryAffixBase>("ReliquaryMainAffix", r => r.Id, r => r.Type, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default)
|
||||
public ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, Weapon>("Weapon", w => w.Id, token);
|
||||
return FromCacheAsDictionaryAsync<WeaponId, Weapon>("Weapon", w => w.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User.User;
|
||||
|
||||
|
||||
@@ -27,9 +27,4 @@ public enum UserOptionResult
|
||||
/// 用户的Cookie成功更新
|
||||
/// </summary>
|
||||
Updated,
|
||||
|
||||
/// <summary>
|
||||
/// 升级到Stoken
|
||||
/// </summary>
|
||||
Upgraded,
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Message;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User.User;
|
||||
@@ -24,6 +23,7 @@ internal class UserService : IUserService
|
||||
{
|
||||
private readonly IServiceScopeFactory scopeFactory;
|
||||
private readonly IMessenger messenger;
|
||||
private readonly object currentUserLocker = new();
|
||||
|
||||
private BindingUser? currentUser;
|
||||
private ObservableCollection<BindingUser>? userCollection;
|
||||
@@ -51,34 +51,35 @@ internal class UserService : IUserService
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
lock (currentUserLocker)
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// only update when not processing a deletion
|
||||
if (value != null)
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// only update when not processing a deletion
|
||||
if (value != null)
|
||||
{
|
||||
if (currentUser != null)
|
||||
{
|
||||
currentUser.IsSelected = false;
|
||||
appDbContext.Users.UpdateAndSave(currentUser.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
UserChangedMessage message = new() { OldValue = currentUser, NewValue = value };
|
||||
|
||||
// 当删除到无用户时也能正常反应状态
|
||||
currentUser = value;
|
||||
|
||||
if (currentUser != null)
|
||||
{
|
||||
currentUser.IsSelected = false;
|
||||
appDbContext.Users.Update(currentUser.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
currentUser.IsSelected = true;
|
||||
appDbContext.Users.UpdateAndSave(currentUser.Entity);
|
||||
}
|
||||
|
||||
messenger.Send(message);
|
||||
}
|
||||
|
||||
Message.UserChangedMessage message = new() { OldValue = currentUser, NewValue = value };
|
||||
|
||||
// 当删除到无用户时也能正常反应状态
|
||||
currentUser = value;
|
||||
|
||||
if (currentUser != null)
|
||||
{
|
||||
currentUser.IsSelected = true;
|
||||
appDbContext.Users.Update(currentUser.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
messenger.Send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +91,7 @@ internal class UserService : IUserService
|
||||
|
||||
// Sync cache
|
||||
userCollection!.Remove(user);
|
||||
roleCollection!.RemoveWhere(r => r.User.InnerId == user.Entity.InnerId);
|
||||
roleCollection?.RemoveWhere(r => r.User.InnerId == user.Entity.InnerId);
|
||||
|
||||
// Sync database
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
@@ -166,7 +167,9 @@ internal class UserService : IUserService
|
||||
{
|
||||
if (roleCollection != null)
|
||||
{
|
||||
return roleCollection.Single(r => r.Role.GameUid == uid).Role;
|
||||
// System.InvalidOperationException: Sequence contains no matching element
|
||||
// Not quiet sure why this happen when its Single()
|
||||
return roleCollection.SingleOrDefault(r => r.Role.GameUid == uid)?.Role;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -177,7 +180,7 @@ internal class UserService : IUserService
|
||||
{
|
||||
Must.NotNull(userCollection!);
|
||||
|
||||
string? mid = await cookie.GetMidAsync().ConfigureAwait(false);
|
||||
string? mid = cookie.GetValueOrDefault(Cookie.MID);
|
||||
|
||||
if (mid == null)
|
||||
{
|
||||
@@ -191,22 +194,19 @@ internal class UserService : IUserService
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// 检查 stoken 是否存在
|
||||
if (user.HasStoken)
|
||||
if (cookie.TryGetAsStoken(out Cookie? stoken))
|
||||
{
|
||||
// update stoken
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
user.UpdateSToken(cookie);
|
||||
user.Stoken = stoken;
|
||||
user.Ltoken = cookie.TryGetAsLtoken(out Cookie? ltoken) ? ltoken : user.Ltoken;
|
||||
user.CookieToken = cookie.TryGetAsCookieToken(out Cookie? cookieToken) ? cookieToken : user.CookieToken;
|
||||
|
||||
appDbContext.Users.UpdateAndSave(user.Entity);
|
||||
|
||||
return new(UserOptionResult.Upgraded, mid);
|
||||
return new(UserOptionResult.Updated, mid);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(UserOptionResult.Invalid, "必须包含 Stoken");
|
||||
}
|
||||
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
user.Cookie = cookie;
|
||||
appDbContext.Users.UpdateAndSave(user.Entity);
|
||||
|
||||
return new(UserOptionResult.Updated, mid);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -245,7 +245,7 @@ internal class UserService : IUserService
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(UserOptionResult.Invalid, "输入的Cookie无法获取用户信息");
|
||||
return new(UserOptionResult.Invalid, "输入的 Cookie 无法获取用户信息");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
|
||||
<PackageCertificateThumbprint>F8C2255969BEA4A681CED102771BF807856AEC02</PackageCertificateThumbprint>
|
||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
|
||||
<AppxSymbolPackageEnabled>True</AppxSymbolPackageEnabled>
|
||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||
<AppxBundle>Never</AppxBundle>
|
||||
@@ -43,6 +43,7 @@
|
||||
<None Remove="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||
<None Remove="Resource\Icon\UI_BtnIcon_Gacha.png" />
|
||||
<None Remove="Resource\Icon\UI_ChapterIcon_Hutao.png" />
|
||||
<None Remove="Resource\Icon\UI_GachaShowPanel_Bg_Weapon.png" />
|
||||
<None Remove="Resource\Icon\UI_GuideIcon_PlayMethod.png" />
|
||||
<None Remove="Resource\Icon\UI_Icon_Achievement.png" />
|
||||
<None Remove="Resource\Icon\UI_Icon_BoostUp.png" />
|
||||
@@ -66,11 +67,13 @@
|
||||
<None Remove="View\Dialog\AchievementImportDialog.xaml" />
|
||||
<None Remove="View\Dialog\AvatarInfoQueryDialog.xaml" />
|
||||
<None Remove="View\Dialog\DailyNoteNotificationDialog.xaml" />
|
||||
<None Remove="View\Dialog\DailyNoteVerificationDialog.xaml" />
|
||||
<None Remove="View\Dialog\GachaLogImportDialog.xaml" />
|
||||
<None Remove="View\Dialog\GachaLogRefreshProgressDialog.xaml" />
|
||||
<None Remove="View\Dialog\GachaLogUrlDialog.xaml" />
|
||||
<None Remove="View\Dialog\GameAccountNameDialog.xaml" />
|
||||
<None Remove="View\Dialog\LoginMihoyoBBSDialog.xaml" />
|
||||
<None Remove="View\Dialog\SignInWebViewDialog.xaml" />
|
||||
<None Remove="View\Dialog\UserDialog.xaml" />
|
||||
<None Remove="View\MainView.xaml" />
|
||||
<None Remove="View\Page\AchievementPage.xaml" />
|
||||
@@ -85,6 +88,7 @@
|
||||
<None Remove="View\Page\LoginMihoyoUserPage.xaml" />
|
||||
<None Remove="View\Page\SettingPage.xaml" />
|
||||
<None Remove="View\Page\WikiAvatarPage.xaml" />
|
||||
<None Remove="View\Page\WikiWeaponPage.xaml" />
|
||||
<None Remove="View\TitleView.xaml" />
|
||||
<None Remove="View\UserView.xaml" />
|
||||
</ItemGroup>
|
||||
@@ -109,6 +113,7 @@
|
||||
<Content Include="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||
<Content Include="Resource\Icon\UI_BtnIcon_Gacha.png" />
|
||||
<Content Include="Resource\Icon\UI_ChapterIcon_Hutao.png" />
|
||||
<Content Include="Resource\Icon\UI_GachaShowPanel_Bg_Weapon.png" />
|
||||
<Content Include="Resource\Icon\UI_GuideIcon_PlayMethod.png" />
|
||||
<Content Include="Resource\Icon\UI_Icon_Achievement.png" />
|
||||
<Content Include="Resource\Icon\UI_Icon_BoostUp.png" />
|
||||
@@ -145,7 +150,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25231-preview" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25247-preview" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@@ -169,6 +174,21 @@
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Page\WikiWeaponPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\DailyNoteVerificationDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\SignInWebViewDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\LoginMihoyoBBSDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<StackPanel>
|
||||
<StackPanel Name="BackgroundStack">
|
||||
<ContentPresenter
|
||||
Name="ContentHost"/>
|
||||
<TextBlock
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Control;
|
||||
|
||||
namespace Snap.Hutao.View.Control;
|
||||
@@ -15,7 +16,8 @@ namespace Snap.Hutao.View.Control;
|
||||
public sealed partial class BottomTextControl : ContentControl
|
||||
{
|
||||
private static readonly DependencyProperty TextProperty = Property<BottomTextControl>.Depend(nameof(Text), string.Empty, OnTextChanged);
|
||||
private static readonly DependencyProperty TopContentProperty = Property<BottomTextControl>.Depend<UIElement>(nameof(TopContent), default!, OnContentChanged2);
|
||||
private static readonly DependencyProperty TopContentProperty = Property<BottomTextControl>.Depend<UIElement>(nameof(TopContent), default!, OnContentChanged);
|
||||
private static readonly DependencyProperty FillProperty = Property<BottomTextControl>.Depend(nameof(Fill), default(Brush), OnFillChanged);
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的底部带有文本的控件
|
||||
@@ -43,13 +45,27 @@ public sealed partial class BottomTextControl : ContentControl
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
private static void OnTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs dp)
|
||||
/// <summary>
|
||||
/// 填充
|
||||
/// </summary>
|
||||
public Brush Fill
|
||||
{
|
||||
((BottomTextControl)sender).TextHost.Text = (string)dp.NewValue;
|
||||
get => (Brush)GetValue(FillProperty);
|
||||
set => SetValue(FillProperty, value);
|
||||
}
|
||||
|
||||
private static void OnContentChanged2(DependencyObject sender, DependencyPropertyChangedEventArgs dp)
|
||||
private static void OnTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
((BottomTextControl)sender).ContentHost.Content = dp.NewValue;
|
||||
((BottomTextControl)sender).TextHost.Text = (string)args.NewValue;
|
||||
}
|
||||
|
||||
private static void OnContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
((BottomTextControl)sender).ContentHost.Content = args.NewValue;
|
||||
}
|
||||
|
||||
private static void OnFillChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
((BottomTextControl)sender).BackgroundStack.Background = (Brush)args.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
xmlns:shcp="using:Snap.Hutao.Control.Panel"
|
||||
xmlns:shmbg="using:Snap.Hutao.Model.Binding.Gacha"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Converter"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance shmbg:TypedWishSummary}">
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
<SolidColorBrush x:Key="PurpleBrush" Color="#FFA156E0"/>
|
||||
<SolidColorBrush x:Key="OrangeBrush" Color="#FFBC6932"/>
|
||||
|
||||
<cwuconv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
|
||||
<DataTemplate x:Key="OrangeListTemplate" x:DataType="shmbg:SummaryItem">
|
||||
<Grid Margin="0,4,4,0" Background="Transparent" >
|
||||
<ToolTipService.ToolTip>
|
||||
@@ -67,47 +65,31 @@
|
||||
|
||||
<DataTemplate x:Key="OrangeGridTemplate" x:DataType="shmbg:SummaryItem">
|
||||
<Grid Width="40" Margin="0,4,4,0">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="{Binding TimeFormatted}"/>
|
||||
</ToolTipService.ToolTip>
|
||||
<StackPanel>
|
||||
<shci:CachedImage
|
||||
<Border
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}"
|
||||
ToolTipService.ToolTip="{Binding TimeFormatted}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="QUALITY_ORANGE"
|
||||
Height="40" Width="40"/>
|
||||
<!--<shci:CachedImage
|
||||
Source="{Binding Icon}"
|
||||
Height="40" Width="40"/>
|
||||
<TextBlock
|
||||
Text="{Binding LastPull}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
HorizontalAlignment="Center"
|
||||
TextWrapping="NoWrap">
|
||||
<TextBlock.Foreground>
|
||||
<SolidColorBrush Color="{Binding Color}"/>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!--<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,0,8,0"
|
||||
Foreground="#FF0063FF"
|
||||
Text="保底"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding IsGuarentee,Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,8,0"
|
||||
Text="UP"
|
||||
Foreground="#FFFFA400"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding IsUp,Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
|
||||
<TextBlock
|
||||
Width="20"
|
||||
TextAlignment="Center"
|
||||
Text="{Binding LastPull}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BodyTextBlockStyle}"/>
|
||||
</StackPanel>-->
|
||||
Height="40" Width="40"/>-->
|
||||
<TextBlock
|
||||
Text="{Binding LastPull}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
HorizontalAlignment="Center"
|
||||
TextWrapping="NoWrap">
|
||||
<TextBlock.Foreground>
|
||||
<SolidColorBrush Color="{Binding Color}"/>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -157,61 +139,110 @@
|
||||
<TextBlock Margin="12,0,0,12" Text="抽" VerticalAlignment="Bottom"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid ColumnSpacing="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ProgressBar
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Value="{Binding LastOrangePull}"
|
||||
Maximum="{Binding GuarenteeOrangeThreshold}"
|
||||
Foreground="{StaticResource OrangeBrush}"/>
|
||||
<TextBlock
|
||||
Width="20"
|
||||
TextAlignment="Center"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ProgressRing
|
||||
Margin="4"
|
||||
Height="40"
|
||||
Width="40"
|
||||
Grid.Column="0"
|
||||
IsIndeterminate="False"
|
||||
Maximum="{Binding GuarenteeOrangeThreshold}"
|
||||
Value="{Binding LastOrangePull}"
|
||||
Foreground="{StaticResource OrangeBrush}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{Binding LastOrangePull}"
|
||||
Foreground="{StaticResource OrangeBrush}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="五星"
|
||||
Foreground="{StaticResource OrangeBrush}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding LastOrangePull}"
|
||||
Foreground="{StaticResource OrangeBrush}"/>
|
||||
<ProgressBar
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Value="{Binding LastPurplePull}"
|
||||
Maximum="{Binding GuarenteePurpleThreshold}"
|
||||
Foreground="{StaticResource PurpleBrush}"/>
|
||||
<TextBlock
|
||||
Width="20"
|
||||
TextAlignment="Center"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding LastPurplePull}"
|
||||
Foreground="{StaticResource PurpleBrush}"/>
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ProgressRing
|
||||
Margin="4"
|
||||
Height="40"
|
||||
Width="40"
|
||||
Grid.Column="0"
|
||||
IsIndeterminate="False"
|
||||
Maximum="{Binding GuarenteePurpleThreshold}"
|
||||
Value="{Binding LastPurplePull}"
|
||||
Foreground="{StaticResource PurpleBrush}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{Binding LastPurplePull}"
|
||||
Foreground="{StaticResource PurpleBrush}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="四星"
|
||||
Foreground="{StaticResource PurpleBrush}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<TextBlock
|
||||
Opacity="0.6"
|
||||
FontFamily="Consolas"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding FromFormatted}"/>
|
||||
<TextBlock
|
||||
Opacity="0.6"
|
||||
FontFamily="Consolas"
|
||||
Text="-"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource BodyTextBlockStyle}"/>
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Opacity="0.6"
|
||||
FontFamily="Consolas"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding ToFormatted}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -30,7 +30,14 @@ public sealed partial class AvatarInfoQueryDialog : ContentDialog
|
||||
{
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
|
||||
return new(result == ContentDialogResult.Primary, result == ContentDialogResult.Primary ? new(InputText.Text) : default);
|
||||
bool isOk = result == ContentDialogResult.Primary;
|
||||
|
||||
if (InputText.Text.Length != 9)
|
||||
{
|
||||
return new(false, default);
|
||||
}
|
||||
|
||||
return new(isOk, isOk && InputText.Text.Length == 9 ? new(InputText.Text) : default);
|
||||
}
|
||||
|
||||
private void InputTextChanged(object sender, TextChangedEventArgs e)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.View.Dialog.DailyNoteVerificationDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Closed="OnContentDialogClosed"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
Title="米游社实时便笺"
|
||||
PrimaryButtonText="完成"
|
||||
DefaultButton="Primary">
|
||||
|
||||
<Grid Loaded="OnGridLoaded">
|
||||
<WebView2 Name="WebView" Height="448" Width="380"/>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Web.Bridge;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
/// <summary>
|
||||
/// ʵʱ<CAB5><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
|
||||
/// </summary>
|
||||
public sealed partial class DailyNoteVerificationDialog : ContentDialog
|
||||
{
|
||||
private readonly IServiceScope scope;
|
||||
private readonly User user;
|
||||
private readonly PlayerUid uid;
|
||||
[SuppressMessage("", "IDE0052")]
|
||||
private DailyNoteJsInterface? dailyNoteJsInterface;
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD>ʵʱ<CAB5><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
|
||||
/// </summary>
|
||||
/// <param name="window"><3E><><EFBFBD><EFBFBD></param>
|
||||
/// <param name="user"><3E>û<EFBFBD></param>
|
||||
/// <param name="uid">uid</param>
|
||||
public DailyNoteVerificationDialog(Window window, User user, PlayerUid uid)
|
||||
{
|
||||
InitializeComponent();
|
||||
XamlRoot = window.Content.XamlRoot;
|
||||
this.user = user;
|
||||
this.uid = uid;
|
||||
scope = Ioc.Default.CreateScope();
|
||||
}
|
||||
|
||||
private void OnGridLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InitializeAsync().SafeForget();
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
await WebView.EnsureCoreWebView2Async();
|
||||
CoreWebView2 coreWebView2 = WebView.CoreWebView2;
|
||||
|
||||
coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent();
|
||||
dailyNoteJsInterface = new(coreWebView2, scope.ServiceProvider);
|
||||
|
||||
#if DEBUG
|
||||
coreWebView2.OpenDevToolsWindow();
|
||||
#endif
|
||||
string query = $"?role_id={uid.Value}&server={uid.Region}";
|
||||
coreWebView2.Navigate($"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/{query}");
|
||||
}
|
||||
|
||||
private void OnContentDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
|
||||
{
|
||||
dailyNoteJsInterface = null;
|
||||
scope.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Web.Hoyolab.Passport;
|
||||
|
||||
@@ -16,7 +15,7 @@ public sealed partial class LoginMihoyoBBSDialog : ContentDialog
|
||||
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µĵ<C2B5>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
|
||||
/// </summary>
|
||||
/// <param name="window"><3E><><EFBFBD><EFBFBD></param>
|
||||
public LoginMihoyoBBSDialog(Window window)
|
||||
public LoginMihoyoBBSDialog(MainWindow window)
|
||||
{
|
||||
InitializeComponent();
|
||||
XamlRoot = window.Content.XamlRoot;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user