mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77db918178 | ||
|
|
77158fc708 | ||
|
|
f2d63e69ea |
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -48,9 +48,9 @@ body:
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 相关的崩溃日志 位于 `%HOMEPATH%/Documents/Hutao/Log.db`
|
||||
label: 相关的崩溃日志
|
||||
description: |
|
||||
在资源管理器中直接输入`%HOMEPATH%/Documents/Hutao`即可进入文件夹
|
||||
在资源管理器中直接输入`%userprofile%/Documents/Hutao`即可进入文件夹
|
||||
如果应用程序崩溃了,请将`log.db` 文件上传,文件包含了敏感信息,谨慎上传
|
||||
如果这个表单是关于导入祈愿记录的问题,请包含你导入的`Json`文件
|
||||
**务必不要上传`user.db`文件,该文件包含你的帐号敏感信息**
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,6 +11,10 @@ src/Snap.Hutao/Snap.Hutao/bin/
|
||||
src/Snap.Hutao/Snap.Hutao/obj/
|
||||
src/Snap.Hutao/Snap.Hutao/Snap.Hutao_TemporaryKey.pfx
|
||||
|
||||
src/Snap.Hutao/Snap.Hutao.Installer/bin/
|
||||
src/Snap.Hutao/Snap.Hutao.Installer/obj/
|
||||
src/Snap.Hutao/Snap.Hutao.Installer/Properties/PublishProfiles/FolderProfile.pubxml.user
|
||||
|
||||
src/Snap.Hutao/Snap.Hutao.SourceGeneration/bin/
|
||||
src/Snap.Hutao/Snap.Hutao.SourceGeneration/obj/
|
||||
|
||||
|
||||
@@ -36,5 +36,4 @@
|
||||
* [microsoft/vs-threading](https://github.com/microsoft/vs-threading)
|
||||
* [microsoft/vs-validation](https://github.com/microsoft/vs-validation)
|
||||
* [microsoft/WindowsAppSDK](https://github.com/microsoft/WindowsAppSDK)
|
||||
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
|
||||
* [MiniExcel/MiniExcel](https://github.com/MiniExcel/MiniExcel)
|
||||
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
|
||||
72
src/Snap.Hutao/Snap.Hutao.Installer/Program.cs
Normal file
72
src/Snap.Hutao/Snap.Hutao.Installer/Program.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Installer;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private const string AppxKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Appx";
|
||||
private const string ValueName = "AllowDevelopmentWithoutDevLicense";
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
string ps1File = Path.Combine(AppContext.BaseDirectory, "Install.ps1");
|
||||
|
||||
if (!File.Exists(ps1File))
|
||||
{
|
||||
Console.WriteLine("未检测到 Install.ps1 文件");
|
||||
Console.WriteLine("请勿移动该安装程序,按下任意键退出...");
|
||||
Console.ReadKey();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//以管理策略打开开发者模式
|
||||
Registry.SetValue(AppxKey, ValueName, 1, RegistryValueKind.DWord);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine("开发者模式未开启,请手动开启,参阅下方链接");
|
||||
Console.WriteLine("https://learn.microsoft.com/zh-CN/windows/apps/get-started/developer-mode-features-and-debugging");
|
||||
}
|
||||
|
||||
await InstallAsync(ps1File).ConfigureAwait(false);
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("官方文档与使用教程");
|
||||
Console.WriteLine("https://hut.ao");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("在开始菜单中启动 Snap.Hutao ,按下任意键退出...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
|
||||
private static async Task InstallAsync(string ps1File)
|
||||
{
|
||||
Console.WriteLine("请注意 PowerShell 中的提示");
|
||||
Process ps = new()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
Arguments = $"-ExecutionPolicy Unrestricted \"{ps1File}\"",
|
||||
UseShellExecute = true,
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
ps.Start();
|
||||
await ps.WaitForExitAsync();
|
||||
Console.WriteLine("安装脚本运行完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
79
src/Snap.Hutao/Snap.Hutao.Installer/app.manifest
Normal file
79
src/Snap.Hutao/Snap.Hutao.Installer/app.manifest
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC 清单选项
|
||||
如果想要更改 Windows 用户帐户控制级别,请使用
|
||||
以下节点之一替换 requestedExecutionLevel 节点。
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
|
||||
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
|
||||
元素。
|
||||
-->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
|
||||
Windows 版本的列表。取消评论适当的元素,
|
||||
Windows 将自动选择最兼容的环境。 -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
|
||||
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
|
||||
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
|
||||
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
|
||||
|
||||
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
|
||||
<!--
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
-->
|
||||
|
||||
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
|
||||
<!--
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
</assembly>
|
||||
@@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsUI", "SettingsUI\Se
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snap.Hutao.Installer", "Snap.Hutao.Installer\Snap.Hutao.Installer.csproj", "{CEC01691-F65E-4874-9AE2-F571369A7631}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -82,6 +84,22 @@ Global
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|arm64.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|arm64.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|arm64.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CEC01691-F65E-4874-9AE2-F571369A7631}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
|
||||
<!--Pivot Resource-->
|
||||
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
|
||||
<Thickness x:Key="PivotHeaderItemMargin">16,0,0,0</Thickness>
|
||||
<Thickness x:Key="PivotItemMargin">0</Thickness>
|
||||
<!--CornerRadius-->
|
||||
<CornerRadius x:Key="CompatCornerRadius">6</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusTop">6,6,0,0</CornerRadius>
|
||||
@@ -46,6 +48,22 @@
|
||||
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
|
||||
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
|
||||
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
|
||||
|
||||
<!--Styles-->
|
||||
<Style
|
||||
x:Key="LargeGridViewItemStyle"
|
||||
TargetType="GridViewItem"
|
||||
BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
<!--ItemsPanelTemplate-->
|
||||
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
|
||||
<ItemsStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
|
||||
<ItemsPanelTemplate x:Key="HorizontalStackPanelTemplate">
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -7,9 +7,7 @@ using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Exception;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.AppCenter;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using System.Diagnostics;
|
||||
using Windows.Storage;
|
||||
@@ -29,13 +27,13 @@ public partial class App : Application
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="appCenter">App Center</param>
|
||||
public App(ILogger<App> logger, AppCenter appCenter)
|
||||
public App(ILogger<App> logger)
|
||||
{
|
||||
// load app resource
|
||||
InitializeComponent();
|
||||
this.logger = logger;
|
||||
|
||||
_ = new ExceptionRecorder(this, logger, appCenter);
|
||||
_ = new ExceptionRecorder(this, logger);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -61,10 +59,6 @@ public partial class App : Application
|
||||
.ImplictAs<IMetadataInitializer>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget(logger);
|
||||
|
||||
Ioc.Default
|
||||
.GetRequiredService<AppCenter>()
|
||||
.Initialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@ public class ScopedPage : Page
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// 应当在 InitializeComponent() 前调用
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">视图模型类型</typeparam>
|
||||
public void InitializeWith<TViewModel>()
|
||||
|
||||
@@ -18,5 +18,5 @@ internal interface ISupportAsyncInitialization
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>初始化任务</returns>
|
||||
ValueTask<bool> InitializeAsync(CancellationToken token = default);
|
||||
ValueTask<bool> InitializeAsync();
|
||||
}
|
||||
@@ -53,9 +53,9 @@ internal static class CoreEnvironment
|
||||
public static readonly string HoyolabDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// AppCenter 设备Id
|
||||
/// 胡桃设备Id
|
||||
/// </summary>
|
||||
public static readonly string AppCenterDeviceId;
|
||||
public static readonly string HutaoDeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 默认的Json序列化选项
|
||||
@@ -78,7 +78,7 @@ internal static class CoreEnvironment
|
||||
|
||||
// simply assign a random guid
|
||||
HoyolabDeviceId = Guid.NewGuid().ToString();
|
||||
AppCenterDeviceId = GetUniqueUserID();
|
||||
HutaoDeviceId = GetUniqueUserID();
|
||||
}
|
||||
|
||||
private static string GetUniqueUserID()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Service.AppCenter;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
|
||||
namespace Snap.Hutao.Core.Exception;
|
||||
|
||||
@@ -13,26 +13,24 @@ namespace Snap.Hutao.Core.Exception;
|
||||
internal class ExceptionRecorder
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly AppCenter appCenter;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的异常记录器
|
||||
/// </summary>
|
||||
/// <param name="application">应用程序</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="appCenter">App Center</param>
|
||||
public ExceptionRecorder(Application application, ILogger logger, AppCenter appCenter)
|
||||
public ExceptionRecorder(Application application, ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.appCenter = appCenter;
|
||||
|
||||
application.UnhandledException += OnAppUnhandledException;
|
||||
application.DebugSettings.BindingFailed += OnXamlBindingFailed;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
appCenter.TrackCrash(e.Exception);
|
||||
Ioc.Default.GetRequiredService<HomaClient2>().UploadLogAsync(e.Exception).GetAwaiter().GetResult();
|
||||
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
|
||||
|
||||
foreach (ILoggerProvider provider in Ioc.Default.GetRequiredService<IEnumerable<ILoggerProvider>>())
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the task for a cancellation token, as well as the token registration. The registration is disposed when this instance is disposed.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">包装类型</typeparam>
|
||||
public sealed class CancellationTokenTaskCompletionSource : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The cancellation token registration, if any. This is <c>null</c> if the registration was not necessary.
|
||||
/// </summary>
|
||||
private readonly IDisposable? registration;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a task for the specified cancellation token, registering with the token if necessary.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token to observe.</param>
|
||||
public CancellationTokenTaskCompletionSource(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Task = Task.CompletedTask;
|
||||
return;
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource();
|
||||
registration = cancellationToken.Register(() => tcs.TrySetResult(), useSynchronizationContext: false);
|
||||
Task = tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task for the source cancellation token.
|
||||
/// </summary>
|
||||
public Task Task { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the cancellation token registration, if any. Note that this may cause <see cref="Task"/> to never complete.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
registration?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -28,4 +28,4 @@ internal class ConcurrentCancellationTokenSource<TItem>
|
||||
|
||||
return waitingItems.GetOrAdd(item, new CancellationTokenSource()).Token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Snap.Hutao.Core;
|
||||
/// </summary>
|
||||
internal abstract class WebView2Helper
|
||||
{
|
||||
private static bool hasEverDetected = false;
|
||||
private static bool isSupported = false;
|
||||
private static bool hasEverDetected;
|
||||
private static bool isSupported;
|
||||
private static string version = "未检测到 WebView2 运行时";
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -160,5 +160,9 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
// 48 is the navigation button leftInset
|
||||
RectInt32 dragRect = new RectInt32(48, 0, (int)titleBar.ActualWidth, (int)titleBar.ActualHeight).Scale(scale);
|
||||
appTitleBar.SetDragRectangles(dragRect.Enumerate().ToArray());
|
||||
|
||||
// workaround for https://github.com/microsoft/WindowsAppSDK/issues/2976
|
||||
// add this to set the same window size after every time drag rectangles are set
|
||||
appWindow.ResizeClient(appWindow.ClientSize);
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ public class SystemBackdrop
|
||||
|
||||
private class DispatcherQueueHelper
|
||||
{
|
||||
private object? dispatcherQueueController = null;
|
||||
private object? dispatcherQueueController;
|
||||
|
||||
/// <summary>
|
||||
/// 确保系统调度队列控制器存在
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Service.AppCenter;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
|
||||
namespace Snap.Hutao.Factory;
|
||||
|
||||
@@ -13,17 +13,14 @@ namespace Snap.Hutao.Factory;
|
||||
internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
||||
{
|
||||
private readonly ILogger<AsyncRelayCommandFactory> logger;
|
||||
private readonly AppCenter appCenter;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的异步命令工厂
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="appCenter">App Center</param>
|
||||
public AsyncRelayCommandFactory(ILogger<AsyncRelayCommandFactory> logger, AppCenter appCenter)
|
||||
public AsyncRelayCommandFactory(ILogger<AsyncRelayCommandFactory> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.appCenter = appCenter;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -86,6 +83,7 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
||||
return command;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
private void ReportException(IAsyncRelayCommand command)
|
||||
{
|
||||
command.PropertyChanged += (sender, args) =>
|
||||
@@ -98,7 +96,7 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
||||
{
|
||||
Exception baseException = exception.GetBaseException();
|
||||
logger.LogError(EventIds.AsyncCommandException, baseException, "{name} Exception", nameof(AsyncRelayCommand));
|
||||
appCenter.TrackError(exception);
|
||||
Ioc.Default.GetRequiredService<HomaClient2>().UploadLogAsync(baseException).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ global using Microsoft.Extensions.Logging;
|
||||
// Snap.Hutao
|
||||
global using Snap.Hutao.Core.DependencyInjection;
|
||||
global using Snap.Hutao.Core.DependencyInjection.Annotation;
|
||||
global using Snap.Hutao.Core.Threading;
|
||||
global using Snap.Hutao.Core.Validation;
|
||||
|
||||
// Runtime
|
||||
|
||||
@@ -8,12 +8,6 @@ namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
/// </summary>
|
||||
public class Reliquary : EquipBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 副属性列表
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public List<ReliquarySubProperty> SubProperties { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 初始词条
|
||||
/// </summary>
|
||||
|
||||
@@ -21,6 +21,7 @@ public class ComplexAvatar
|
||||
{
|
||||
Name = avatar.Name;
|
||||
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
|
||||
SideIcon = AvatarSideIconConverter.IconNameToUri(avatar.SideIcon);
|
||||
Quality = avatar.Quality;
|
||||
Rate = $"{rate:P3}";
|
||||
}
|
||||
@@ -35,6 +36,11 @@ public class ComplexAvatar
|
||||
/// </summary>
|
||||
public Uri Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 侧面图标
|
||||
/// </summary>
|
||||
public Uri SideIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 星级
|
||||
/// </summary>
|
||||
|
||||
@@ -31,6 +31,6 @@ public class UIAF
|
||||
/// <returns>当前UIAF对象是否受支持</returns>
|
||||
public bool IsCurrentVersionSupported()
|
||||
{
|
||||
return SupportedVersion.Contains(Info.UIAFVersion ?? string.Empty);
|
||||
return SupportedVersion.Contains(Info?.UIAFVersion ?? string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,9 @@ public static class AvatarIds
|
||||
public static readonly AvatarId Ayaka = 10000002;
|
||||
public static readonly AvatarId Qin = 10000003;
|
||||
|
||||
public static readonly AvatarId PlayerBoy = 10000005;
|
||||
public static readonly AvatarId Lisa = 10000006;
|
||||
public static readonly AvatarId PlayerGirl = 10000007;
|
||||
|
||||
public static readonly AvatarId Barbara = 10000014;
|
||||
public static readonly AvatarId Kaeya = 10000015;
|
||||
@@ -75,4 +77,16 @@ public static class AvatarIds
|
||||
public static readonly AvatarId Candace = 10000072;
|
||||
public static readonly AvatarId Nahida = 10000073;
|
||||
public static readonly AvatarId Layla = 10000074;
|
||||
public static readonly AvatarId Wanderer = 10000075;
|
||||
public static readonly AvatarId Faruzan = 10000076;
|
||||
|
||||
/// <summary>
|
||||
/// 检查该角色是否为主角
|
||||
/// </summary>
|
||||
/// <param name="avatarId">角色Id</param>
|
||||
/// <returns>角色是否为主角</returns>
|
||||
public static bool IsPlayer(AvatarId avatarId)
|
||||
{
|
||||
return avatarId == PlayerBoy || avatarId == PlayerGirl;
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,19 @@ internal class AvatarSideIconConverter : ValueConverterBase<string, Uri>
|
||||
{
|
||||
private const string BaseUrl = "https://static.snapgenshin.com/AvatarIcon/{0}.png";
|
||||
|
||||
/// <summary>
|
||||
/// 名称转Uri
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns>链接</returns>
|
||||
public static Uri IconNameToUri(string name)
|
||||
{
|
||||
return new Uri(string.Format(BaseUrl, name));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Uri Convert(string from)
|
||||
{
|
||||
return new Uri(string.Format(BaseUrl, from));
|
||||
return IconNameToUri(from);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.1.18.0" />
|
||||
Version="1.1.21.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_210.png
Normal file
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_210.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
@@ -1,95 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Service.AppCenter.Model;
|
||||
using Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
public sealed class AppCenter : IDisposable
|
||||
{
|
||||
private const string AppSecret = "de5bfc48-17fc-47ee-8e7e-dee7dc59d554";
|
||||
private const string API = "https://in.appcenter.ms/logs?api-version=1.0.0";
|
||||
|
||||
private readonly TaskCompletionSource uploadTaskCompletionSource = new();
|
||||
private readonly CancellationTokenSource uploadTaskCancllationTokenSource = new();
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly List<Log> queue;
|
||||
private readonly Device deviceInfo;
|
||||
private readonly JsonSerializerOptions options;
|
||||
|
||||
private Guid sessionID;
|
||||
|
||||
public AppCenter()
|
||||
{
|
||||
options = new(CoreEnvironment.JsonOptions);
|
||||
options.Converters.Add(new LogConverter());
|
||||
|
||||
httpClient = new() { DefaultRequestHeaders = { { "Install-ID", CoreEnvironment.AppCenterDeviceId }, { "App-Secret", AppSecret } } };
|
||||
queue = new List<Log>();
|
||||
deviceInfo = new Device();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (!uploadTaskCancllationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
await UploadAsync().ConfigureAwait(false);
|
||||
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
uploadTaskCompletionSource.TrySetResult();
|
||||
}).SafeForget();
|
||||
}
|
||||
|
||||
public async Task UploadAsync()
|
||||
{
|
||||
if (queue.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string? uploadStatus = null;
|
||||
do
|
||||
{
|
||||
queue.ForEach(log => log.Status = LogStatus.Uploading);
|
||||
LogContainer container = new(queue);
|
||||
|
||||
LogUploadResult? response = await httpClient
|
||||
.TryCatchPostAsJsonAsync<LogContainer, LogUploadResult>(API, container, options)
|
||||
.ConfigureAwait(false);
|
||||
uploadStatus = response?.Status;
|
||||
}
|
||||
while (uploadStatus != "Success");
|
||||
|
||||
queue.RemoveAll(log => log.Status == LogStatus.Uploading);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
sessionID = Guid.NewGuid();
|
||||
queue.Add(new StartServiceLog("Analytics", "Crashes").Initialize(sessionID, deviceInfo));
|
||||
queue.Add(new StartSessionLog().Initialize(sessionID, deviceInfo).Initialize(sessionID, deviceInfo));
|
||||
}
|
||||
|
||||
public void TrackCrash(Exception exception, bool isFatal = true)
|
||||
{
|
||||
queue.Add(new ManagedErrorLog(exception, isFatal).Initialize(sessionID, deviceInfo));
|
||||
}
|
||||
|
||||
public void TrackError(Exception exception)
|
||||
{
|
||||
queue.Add(new HandledErrorLog(exception).Initialize(sessionID, deviceInfo));
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD002")]
|
||||
public void Dispose()
|
||||
{
|
||||
uploadTaskCancllationTokenSource.Cancel();
|
||||
uploadTaskCompletionSource.Task.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.Win32;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter;
|
||||
|
||||
/// <summary>
|
||||
/// 设备帮助类
|
||||
/// </summary>
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public static class DeviceHelper
|
||||
{
|
||||
private static readonly RegistryKey? BiosKey = Registry.LocalMachine.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\BIOS");
|
||||
private static readonly RegistryKey? GeoKey = Registry.CurrentUser.OpenSubKey("Control Panel\\International\\Geo");
|
||||
private static readonly RegistryKey? CurrentVersionKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
|
||||
|
||||
public static string? GetOem()
|
||||
{
|
||||
string? oem = BiosKey?.GetValue("SystemManufacturer") as string;
|
||||
return oem == "System manufacturer" ? null : oem;
|
||||
}
|
||||
|
||||
public static string? GetModel()
|
||||
{
|
||||
string? model = BiosKey?.GetValue("SystemProductName") as string;
|
||||
return model == "System Product Name" ? null : model;
|
||||
}
|
||||
|
||||
public static string GetScreenSize()
|
||||
{
|
||||
RectInt32 screen = DisplayArea.Primary.OuterBounds;
|
||||
return $"{screen.Width}x{screen.Height}";
|
||||
}
|
||||
|
||||
public static string? GetCountry()
|
||||
{
|
||||
return GeoKey?.GetValue("Name") as string;
|
||||
}
|
||||
|
||||
public static string GetSystemVersion()
|
||||
{
|
||||
object? majorVersion = CurrentVersionKey?.GetValue("CurrentMajorVersionNumber");
|
||||
if (majorVersion != null)
|
||||
{
|
||||
object? minorVersion = CurrentVersionKey?.GetValue("CurrentMinorVersionNumber", "0");
|
||||
object? buildNumber = CurrentVersionKey?.GetValue("CurrentBuildNumber", "0");
|
||||
return $"{majorVersion}.{minorVersion}.{buildNumber}";
|
||||
}
|
||||
else
|
||||
{
|
||||
object? version = CurrentVersionKey?.GetValue("CurrentVersion", "0.0");
|
||||
object? buildNumber = CurrentVersionKey?.GetValue("CurrentBuild", "0");
|
||||
return $"{version}.{buildNumber}";
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetSystemBuild()
|
||||
{
|
||||
return (int)(CurrentVersionKey?.GetValue("UBR") ?? 0);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class AppCenterException
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = "UnknownType";
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; set; }
|
||||
|
||||
[JsonPropertyName("stackTrace")]
|
||||
public string? StackTrace { get; set; }
|
||||
|
||||
[JsonPropertyName("innerExceptions")]
|
||||
public List<AppCenterException>? InnerExceptions { get; set; }
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class Device
|
||||
{
|
||||
[JsonPropertyName("sdkName")]
|
||||
public string SdkName { get; set; } = "appcenter.winui";
|
||||
|
||||
[JsonPropertyName("sdkVersion")]
|
||||
public string SdkVersion { get; set; } = "4.5.0";
|
||||
|
||||
[JsonPropertyName("osName")]
|
||||
public string OsName { get; set; } = "WINDOWS";
|
||||
|
||||
[JsonPropertyName("osVersion")]
|
||||
public string OsVersion { get; set; } = DeviceHelper.GetSystemVersion();
|
||||
|
||||
[JsonPropertyName("osBuild")]
|
||||
public string OsBuild { get; set; } = $"{DeviceHelper.GetSystemVersion()}.{DeviceHelper.GetSystemBuild()}";
|
||||
|
||||
[JsonPropertyName("model")]
|
||||
public string? Model { get; set; } = DeviceHelper.GetModel();
|
||||
|
||||
[JsonPropertyName("oemName")]
|
||||
public string? OemName { get; set; } = DeviceHelper.GetOem();
|
||||
|
||||
[JsonPropertyName("screenSize")]
|
||||
public string ScreenSize { get; set; } = DeviceHelper.GetScreenSize();
|
||||
|
||||
[JsonPropertyName("carrierCountry")]
|
||||
public string Country { get; set; } = DeviceHelper.GetCountry() ?? "CN";
|
||||
|
||||
[JsonPropertyName("locale")]
|
||||
public string Locale { get; set; } = CultureInfo.CurrentCulture.Name;
|
||||
|
||||
[JsonPropertyName("timeZoneOffset")]
|
||||
public int TimeZoneOffset { get; set; } = (int)TimeZoneInfo.Local.BaseUtcOffset.TotalMinutes;
|
||||
|
||||
[JsonPropertyName("appVersion")]
|
||||
public string AppVersion { get; set; } = CoreEnvironment.Version.ToString();
|
||||
|
||||
[JsonPropertyName("appBuild")]
|
||||
public string AppBuild { get; set; } = CoreEnvironment.Version.ToString();
|
||||
|
||||
[JsonPropertyName("appNamespace")]
|
||||
public string AppNamespace { get; set; } = typeof(App).Namespace ?? string.Empty;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class EventLog : PropertiesLog
|
||||
{
|
||||
public EventLog(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "event"; }
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class HandledErrorLog : PropertiesLog
|
||||
{
|
||||
public HandledErrorLog(Exception exception)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
Exception = LogHelper.Create(exception);
|
||||
}
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public Guid? Id { get; set; }
|
||||
|
||||
[JsonPropertyName("exception")]
|
||||
public AppCenterException Exception { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "handledError"; }
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public abstract class Log
|
||||
{
|
||||
[JsonIgnore]
|
||||
public LogStatus Status { get; set; } = LogStatus.Pending;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public abstract string Type { get; }
|
||||
|
||||
[JsonPropertyName("sid")]
|
||||
public Guid Session { get; set; }
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public string Timestamp { get; set; } = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ");
|
||||
|
||||
[JsonPropertyName("device")]
|
||||
public Device Device { get; set; } = default!;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class LogContainer
|
||||
{
|
||||
public LogContainer(IEnumerable<Log> logs)
|
||||
{
|
||||
Logs = logs;
|
||||
}
|
||||
|
||||
[JsonPropertyName("logs")]
|
||||
public IEnumerable<Log> Logs { get; set; }
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
/// <summary>
|
||||
/// 日志转换器
|
||||
/// </summary>
|
||||
public class LogConverter : JsonConverter<Log>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Log? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, Log value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public static class LogHelper
|
||||
{
|
||||
public static T Initialize<T>(this T log, Guid sid, Device device)
|
||||
where T : Log
|
||||
{
|
||||
log.Session = sid;
|
||||
log.Device = device;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
public static AppCenterException Create(Exception exception)
|
||||
{
|
||||
AppCenterException current = new()
|
||||
{
|
||||
Type = exception.GetType().ToString(),
|
||||
Message = exception.Message,
|
||||
StackTrace = exception.StackTrace,
|
||||
};
|
||||
|
||||
if (exception is AggregateException aggregateException)
|
||||
{
|
||||
if (aggregateException.InnerExceptions.Count != 0)
|
||||
{
|
||||
current.InnerExceptions = new();
|
||||
foreach (var innerException in aggregateException.InnerExceptions)
|
||||
{
|
||||
current.InnerExceptions.Add(Create(innerException));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exception.InnerException != null)
|
||||
{
|
||||
current.InnerExceptions ??= new();
|
||||
current.InnerExceptions.Add(Create(exception.InnerException));
|
||||
}
|
||||
|
||||
StackTrace stackTrace = new(exception, true);
|
||||
StackFrame[] frames = stackTrace.GetFrames();
|
||||
|
||||
if (frames.Length > 0 && frames[0].HasNativeImage())
|
||||
{
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
[SuppressMessage("", "SA1602")]
|
||||
public enum LogStatus
|
||||
{
|
||||
Pending,
|
||||
Uploading,
|
||||
Uploaded,
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class ManagedErrorLog : Log
|
||||
{
|
||||
public ManagedErrorLog(Exception exception, bool fatal = true)
|
||||
{
|
||||
var p = Process.GetCurrentProcess();
|
||||
Id = Guid.NewGuid();
|
||||
Fatal = fatal;
|
||||
UserId = CoreEnvironment.AppCenterDeviceId;
|
||||
ProcessId = p.Id;
|
||||
Exception = LogHelper.Create(exception);
|
||||
ProcessName = p.ProcessName;
|
||||
Architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
|
||||
AppLaunchTimestamp = p.StartTime.ToUniversalTime();
|
||||
}
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[JsonPropertyName("userId")]
|
||||
public string? UserId { get; set; }
|
||||
|
||||
[JsonPropertyName("processId")]
|
||||
public int ProcessId { get; set; }
|
||||
|
||||
[JsonPropertyName("processName")]
|
||||
public string ProcessName { get; set; }
|
||||
|
||||
[JsonPropertyName("fatal")]
|
||||
public bool Fatal { get; set; }
|
||||
|
||||
[JsonPropertyName("appLaunchTimestamp")]
|
||||
public DateTime? AppLaunchTimestamp { get; set; }
|
||||
|
||||
[JsonPropertyName("architecture")]
|
||||
public string? Architecture { get; set; }
|
||||
|
||||
[JsonPropertyName("exception")]
|
||||
public AppCenterException Exception { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "managedError"; }
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class PageLog : PropertiesLog
|
||||
{
|
||||
public PageLog(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "page"; }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public abstract class PropertiesLog : Log
|
||||
{
|
||||
[JsonPropertyName("properties")]
|
||||
public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class StartServiceLog : Log
|
||||
{
|
||||
public StartServiceLog(params string[] services)
|
||||
{
|
||||
Services = services;
|
||||
}
|
||||
|
||||
[JsonPropertyName("services")]
|
||||
public string[] Services { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "startService"; }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model.Log;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class StartSessionLog : Log
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public override string Type { get => "startSession"; }
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AppCenter.Model;
|
||||
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public class LogUploadResult
|
||||
{
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("validDiagnosticsIds")]
|
||||
public List<Guid> ValidDiagnosticsIds { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("throttledDiagnosticsIds")]
|
||||
public List<Guid> ThrottledDiagnosticsIds { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("correlationId")]
|
||||
public Guid CorrelationId { get; set; }
|
||||
}
|
||||
@@ -2,10 +2,12 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Web.Enka;
|
||||
@@ -51,11 +53,14 @@ internal class AvatarInfoService : IAvatarInfoService
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(PlayerUid uid, RefreshOption refreshOption, CancellationToken token = default)
|
||||
{
|
||||
if (await metadataService.InitializeAsync(token).ConfigureAwait(false))
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (HasOption(refreshOption, RefreshOption.RequestFromAPI))
|
||||
{
|
||||
EnkaResponse? resp = await GetEnkaResponseAsync(uid, token).ConfigureAwait(false);
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (resp == null)
|
||||
{
|
||||
return new(RefreshResult.APIUnavailable, null);
|
||||
@@ -67,7 +72,8 @@ internal class AvatarInfoService : IAvatarInfoService
|
||||
? UpdateDbAvatarInfo(uid.Value, resp.AvatarInfoList)
|
||||
: resp.AvatarInfoList;
|
||||
|
||||
Summary summary = await GetSummaryCoreAsync(resp.PlayerInfo, list).ConfigureAwait(false);
|
||||
Summary summary = await GetSummaryCoreAsync(resp.PlayerInfo, list, token).ConfigureAwait(false);
|
||||
token.ThrowIfCancellationRequested();
|
||||
return new(RefreshResult.Ok, summary);
|
||||
}
|
||||
else
|
||||
@@ -79,7 +85,8 @@ internal class AvatarInfoService : IAvatarInfoService
|
||||
{
|
||||
PlayerInfo info = PlayerInfo.CreateEmpty(uid.Value);
|
||||
|
||||
Summary summary = await GetSummaryCoreAsync(info, GetDbAvatarInfos(uid.Value)).ConfigureAwait(false);
|
||||
Summary summary = await GetSummaryCoreAsync(info, GetDbAvatarInfos(uid.Value), token).ConfigureAwait(false);
|
||||
token.ThrowIfCancellationRequested();
|
||||
return new(RefreshResult.Ok, summary);
|
||||
}
|
||||
}
|
||||
@@ -94,10 +101,10 @@ internal class AvatarInfoService : IAvatarInfoService
|
||||
return (source & define) == define;
|
||||
}
|
||||
|
||||
private async Task<Summary> GetSummaryCoreAsync(PlayerInfo info, IEnumerable<Web.Enka.Model.AvatarInfo> avatarInfos)
|
||||
private async Task<Summary> GetSummaryCoreAsync(PlayerInfo info, IEnumerable<Web.Enka.Model.AvatarInfo> avatarInfos, CancellationToken token)
|
||||
{
|
||||
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
|
||||
Summary summary = await summaryFactory.CreateAsync(info, avatarInfos).ConfigureAwait(false);
|
||||
Summary summary = await summaryFactory.CreateAsync(info, avatarInfos, token).ConfigureAwait(false);
|
||||
logger.LogInformation(EventIds.AvatarInfoGeneration, "AvatarInfoSummary Generation toke {time} ms.", stopwatch.GetElapsedTime().TotalMilliseconds);
|
||||
|
||||
return summary;
|
||||
@@ -117,7 +124,7 @@ internal class AvatarInfoService : IAvatarInfoService
|
||||
|
||||
foreach (Web.Enka.Model.AvatarInfo webInfo in webInfos)
|
||||
{
|
||||
if (webInfo.AvatarId == 10000005 || webInfo.AvatarId == 10000007)
|
||||
if (AvatarIds.IsPlayer(webInfo.AvatarId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -127,28 +134,31 @@ internal class AvatarInfoService : IAvatarInfoService
|
||||
if (entity == null)
|
||||
{
|
||||
entity = Model.Entity.AvatarInfo.Create(uid, webInfo);
|
||||
appDbContext.Add(entity);
|
||||
appDbContext.AvatarInfos.AddAndSave(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.Info = webInfo;
|
||||
appDbContext.Update(entity);
|
||||
appDbContext.AvatarInfos.UpdateAndSave(entity);
|
||||
}
|
||||
}
|
||||
|
||||
appDbContext.SaveChanges();
|
||||
|
||||
return GetDbAvatarInfos(uid);
|
||||
}
|
||||
|
||||
private List<Web.Enka.Model.AvatarInfo> GetDbAvatarInfos(string uid)
|
||||
{
|
||||
return appDbContext.AvatarInfos
|
||||
.Where(i => i.Uid == uid)
|
||||
.Select(i => i.Info)
|
||||
|
||||
// .AsEnumerable()
|
||||
// .OrderByDescending(i => i.AvatarId)
|
||||
.ToList();
|
||||
try
|
||||
{
|
||||
return appDbContext.AvatarInfos
|
||||
.Where(i => i.Uid == uid)
|
||||
.Select(i => i.Info)
|
||||
.ToList();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// appDbContext can be disposed unexpectedly
|
||||
return new();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ internal interface ISummaryFactory
|
||||
/// </summary>
|
||||
/// <param name="playerInfo">玩家信息</param>
|
||||
/// <param name="avatarInfos">角色列表</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>简述对象</returns>
|
||||
Task<Summary> CreateAsync(PlayerInfo playerInfo, IEnumerable<Web.Enka.Model.AvatarInfo> avatarInfos);
|
||||
Task<Summary> CreateAsync(PlayerInfo playerInfo, IEnumerable<Web.Enka.Model.AvatarInfo> avatarInfos, CancellationToken token);
|
||||
}
|
||||
@@ -31,15 +31,15 @@ internal class SummaryFactory : ISummaryFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Summary> CreateAsync(ModelPlayerInfo playerInfo, IEnumerable<ModelAvatarInfo> avatarInfos)
|
||||
public async Task<Summary> CreateAsync(ModelPlayerInfo playerInfo, IEnumerable<ModelAvatarInfo> avatarInfos, CancellationToken token)
|
||||
{
|
||||
Dictionary<int, MetadataAvatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, MetadataWeapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, FightProperty> idRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, ReliquaryAffix> idReliquaryAffixMap = await metadataService.GetIdReliquaryAffixMapAsync().ConfigureAwait(false);
|
||||
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);
|
||||
|
||||
List<ReliquaryLevel> reliqueryLevels = await metadataService.GetReliquaryLevelsAsync().ConfigureAwait(false);
|
||||
List<MetadataReliquary> reliquaries = await metadataService.GetReliquariesAsync().ConfigureAwait(false);
|
||||
List<ReliquaryLevel> reliqueryLevels = await metadataService.GetReliquaryLevelsAsync(token).ConfigureAwait(false);
|
||||
List<MetadataReliquary> reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false);
|
||||
|
||||
SummaryFactoryImplementation inner = new(idAvatarMap, idWeaponMap, idRelicMainPropMap, idReliquaryAffixMap, reliqueryLevels, reliquaries);
|
||||
return inner.Create(playerInfo, avatarInfos);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
|
||||
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
|
||||
@@ -60,7 +61,7 @@ internal class SummaryFactoryImplementation
|
||||
return new()
|
||||
{
|
||||
Player = SummaryHelper.CreatePlayer(playerInfo),
|
||||
Avatars = avatarInfos.Select(a =>
|
||||
Avatars = avatarInfos.Where(a => !AvatarIds.IsPlayer(a.AvatarId)).Select(a =>
|
||||
{
|
||||
SummaryAvatarFactory summaryAvatarFactory = new(
|
||||
idAvatarMap,
|
||||
|
||||
@@ -209,7 +209,8 @@ internal static class SummaryHelper
|
||||
(2, 0) => 100,
|
||||
(2, 1) => 80,
|
||||
|
||||
_ => throw Must.NeverHappen(),
|
||||
// TODO: Not quite sure why can we hit this branch.
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -226,16 +227,6 @@ internal static class SummaryHelper
|
||||
return 100 * ((cr * 2) + cd);
|
||||
}
|
||||
|
||||
private static string FormatValue(FormatMethod method, double value)
|
||||
{
|
||||
return method switch
|
||||
{
|
||||
FormatMethod.Integer => Math.Round((double)value, MidpointRounding.AwayFromZero).ToString(),
|
||||
FormatMethod.Percent => string.Format("{0:P1}", value),
|
||||
_ => value.ToString(),
|
||||
};
|
||||
}
|
||||
|
||||
private static FightProperty GetBonusFightProperty(IDictionary<FightProperty, double> fightPropMap)
|
||||
{
|
||||
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY))
|
||||
|
||||
@@ -123,15 +123,15 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeAsync(CancellationToken token = default)
|
||||
public async ValueTask<bool> InitializeAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync(token).ConfigureAwait(false))
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
nameAvatarMap = await metadataService.GetNameToAvatarMapAsync(token).ConfigureAwait(false);
|
||||
nameWeaponMap = await metadataService.GetNameToWeaponMapAsync(token).ConfigureAwait(false);
|
||||
nameAvatarMap = await metadataService.GetNameToAvatarMapAsync().ConfigureAwait(false);
|
||||
nameWeaponMap = await metadataService.GetNameToWeaponMapAsync().ConfigureAwait(false);
|
||||
|
||||
idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
|
||||
idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
|
||||
idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ internal interface IGachaLogService
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>是否初始化成功</returns>
|
||||
ValueTask<bool> InitializeAsync(CancellationToken token = default);
|
||||
ValueTask<bool> InitializeAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 刷新祈愿记录
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Snap.Hutao.Context.Database;
|
||||
@@ -39,7 +40,6 @@ internal class GameService : IGameService
|
||||
/// </summary>
|
||||
/// <param name="scopeFactory">范围工厂</param>
|
||||
/// <param name="memoryCache">内存缓存</param>
|
||||
/// <param name="gameLocators">游戏定位器集合</param>
|
||||
public GameService(IServiceScopeFactory scopeFactory, IMemoryCache memoryCache)
|
||||
{
|
||||
this.scopeFactory = scopeFactory;
|
||||
@@ -59,7 +59,7 @@ internal class GameService : IGameService
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, null), out bool added);
|
||||
SettingEntry entry = appDbContext.Settings.SingleOrAdd(e => e.Key == SettingEntry.GamePath, () => new(SettingEntry.GamePath, string.Empty), out bool added);
|
||||
|
||||
// Cannot find in setting
|
||||
if (added)
|
||||
@@ -89,6 +89,11 @@ internal class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.Value == null)
|
||||
{
|
||||
return new(false, null!);
|
||||
}
|
||||
|
||||
// Set cache and return.
|
||||
string path = memoryCache.Set(GamePathKey, Must.NotNull(entry.Value!));
|
||||
return new(true, path);
|
||||
@@ -139,7 +144,7 @@ internal class GameService : IGameService
|
||||
public MultiChannel GetMultiChannel()
|
||||
{
|
||||
string gamePath = GetGamePathSkipLocator();
|
||||
string configPath = Path.Combine(Path.GetDirectoryName(gamePath)!, ConfigFile);
|
||||
string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFile);
|
||||
|
||||
using (FileStream stream = File.OpenRead(configPath))
|
||||
{
|
||||
@@ -221,6 +226,11 @@ internal class GameService : IGameService
|
||||
|
||||
string gamePath = GetGamePathSkipLocator();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(gamePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html
|
||||
string commandLine = new CommandLineBuilder()
|
||||
.AppendIf("-popupwindow", configuration.IsBorderless)
|
||||
@@ -317,14 +327,6 @@ internal class GameService : IGameService
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
IQueryable<GameAccount> oldAccounts = appDbContext.GameAccounts.Where(a => a.AttachUid == uid);
|
||||
|
||||
foreach (GameAccount account in oldAccounts)
|
||||
{
|
||||
account.UpdateAttachUid(null);
|
||||
appDbContext.GameAccounts.UpdateAndSave(account);
|
||||
}
|
||||
|
||||
gameAccount.UpdateAttachUid(uid);
|
||||
appDbContext.GameAccounts.UpdateAndSave(gameAccount);
|
||||
}
|
||||
@@ -358,7 +360,14 @@ internal class GameService : IGameService
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
appDbContext.GameAccounts.RemoveAndSave(gameAccount);
|
||||
try
|
||||
{
|
||||
appDbContext.GameAccounts.RemoveAndSave(gameAccount);
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
// This gameAccount has already been deleted.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,8 @@ internal class ManualGameLocator : IGameLocator
|
||||
picker.FileTypeFilter.Add(".exe");
|
||||
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
|
||||
|
||||
// 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.
|
||||
if (await picker.PickSingleFileAsync() is StorageFile file)
|
||||
{
|
||||
string path = file.Path;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
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.Service.Metadata;
|
||||
@@ -111,8 +112,8 @@ internal class HutaoCache : IHutaoCache
|
||||
Dictionary<int, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
idAvatarExtendedMap = new(idAvatarMap)
|
||||
{
|
||||
[10000005] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerBoy", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE },
|
||||
[10000007] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerGirl", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE },
|
||||
[AvatarIds.PlayerBoy] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerBoy", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE },
|
||||
[AvatarIds.PlayerGirl] = new() { Name = "旅行者", Icon = "UI_AvatarIcon_PlayerGirl", Quality = Model.Intrinsic.ItemQuality.QUALITY_ORANGE },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,8 @@ internal interface IMetadataService
|
||||
/// <summary>
|
||||
/// 异步初始化服务,尝试更新元数据
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>初始化是否成功</returns>
|
||||
ValueTask<bool> InitializeAsync(CancellationToken token = default);
|
||||
ValueTask<bool> InitializeAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取成就列表
|
||||
|
||||
@@ -69,7 +69,7 @@ internal partial class MetadataService : IMetadataService, IMetadataInitializer,
|
||||
public bool IsInitialized { get => isInitialized; private set => isInitialized = value; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> InitializeAsync(CancellationToken token = default)
|
||||
public async ValueTask<bool> InitializeAsync()
|
||||
{
|
||||
await initializeCompletionSource.Task.ConfigureAwait(false);
|
||||
return IsInitialized;
|
||||
|
||||
@@ -161,6 +161,7 @@ internal class UserService : IUserService
|
||||
if (cookie.ContainsSToken())
|
||||
{
|
||||
// insert stoken
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
userWithSameUid.UpdateSToken(uid, cookie);
|
||||
appDbContext.Users.UpdateAndSave(userWithSameUid.Entity);
|
||||
|
||||
@@ -169,6 +170,7 @@ internal class UserService : IUserService
|
||||
|
||||
if (cookie.ContainsLTokenAndCookieToken())
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
userWithSameUid.Cookie = cookie;
|
||||
appDbContext.Users.UpdateAndSave(userWithSameUid.Entity);
|
||||
|
||||
|
||||
@@ -48,8 +48,10 @@
|
||||
<None Remove="Resource\Icon\UI_Icon_Locked.png" />
|
||||
<None Remove="Resource\Icon\UI_Icon_None.png" />
|
||||
<None Remove="Resource\Icon\UI_ItemIcon_201.png" />
|
||||
<None Remove="Resource\Icon\UI_ItemIcon_210.png" />
|
||||
<None Remove="Resource\Segoe Fluent Icons.ttf" />
|
||||
<None Remove="stylecop.json" />
|
||||
<None Remove="View\Control\BottomTextControl.xaml" />
|
||||
<None Remove="View\Control\DescParamComboBox.xaml" />
|
||||
<None Remove="View\Control\ItemIcon.xaml" />
|
||||
<None Remove="View\Control\SkillPivot.xaml" />
|
||||
@@ -67,6 +69,7 @@
|
||||
<None Remove="View\Page\AnnouncementContentPage.xaml" />
|
||||
<None Remove="View\Page\AnnouncementPage.xaml" />
|
||||
<None Remove="View\Page\AvatarPropertyPage.xaml" />
|
||||
<None Remove="View\Page\DailyNotePage.xaml" />
|
||||
<None Remove="View\Page\GachaLogPage.xaml" />
|
||||
<None Remove="View\Page\HutaoDatabasePage.xaml" />
|
||||
<None Remove="View\Page\LaunchGamePage.xaml" />
|
||||
@@ -104,6 +107,7 @@
|
||||
<Content Include="Resource\Icon\UI_Icon_Locked.png" />
|
||||
<Content Include="Resource\Icon\UI_Icon_None.png" />
|
||||
<Content Include="Resource\Icon\UI_ItemIcon_201.png" />
|
||||
<Content Include="Resource\Icon\UI_ItemIcon_210.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -150,6 +154,16 @@
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Page\DailyNotePage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Control\BottomTextControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\GameAccountNameDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<ContentControl
|
||||
x:Class="Snap.Hutao.View.Control.BottomTextControl"
|
||||
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">
|
||||
|
||||
<Border
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<StackPanel>
|
||||
<ContentPresenter
|
||||
Name="ContentHost"/>
|
||||
<TextBlock
|
||||
Name="TextHost"
|
||||
Margin="0,0,0,2"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxWidth="80"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ContentControl>
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.View.Control;
|
||||
|
||||
/// <summary>
|
||||
/// 底部带有文本的控件
|
||||
/// </summary>
|
||||
[ContentProperty(Name = nameof(TopContent))]
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的底部带有文本的控件
|
||||
/// </summary>
|
||||
public BottomTextControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 顶部内容
|
||||
/// </summary>
|
||||
public UIElement TopContent
|
||||
{
|
||||
get { return (UIElement)GetValue(TopContentProperty); }
|
||||
set { SetValue(TopContentProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get { return (string)GetValue(TextProperty); }
|
||||
set { SetValue(TextProperty, value); }
|
||||
}
|
||||
|
||||
private static void OnTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs dp)
|
||||
{
|
||||
((BottomTextControl)sender).TextHost.Text = (string)dp.NewValue;
|
||||
}
|
||||
|
||||
private static void OnContentChanged2(DependencyObject sender, DependencyPropertyChangedEventArgs dp)
|
||||
{
|
||||
((BottomTextControl)sender).ContentHost.Content = dp.NewValue;
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,11 @@
|
||||
shvh:NavHelper.NavigateTo="shvp:GachaLogPage"
|
||||
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_BtnIcon_Gacha.png}"/>
|
||||
|
||||
<NavigationViewItem
|
||||
Content="实时便笺"
|
||||
shvh:NavHelper.NavigateTo="shvp:DailyNotePage"
|
||||
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_ItemIcon_210.png}"/>
|
||||
|
||||
<NavigationViewItem
|
||||
Content="成就管理"
|
||||
shvh:NavHelper.NavigateTo="shvp:AchievementPage"
|
||||
|
||||
@@ -18,22 +18,22 @@
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.Background>
|
||||
<SolidColorBrush Color="{ThemeResource CardBackgroundFillColorSecondary}"/>
|
||||
</Grid.Background>
|
||||
<Grid Background="{StaticResource CardBackgroundFillColorDefaultBrush}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="252"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<CommandBar
|
||||
Grid.Column="1"
|
||||
DefaultLabelPosition="Right">
|
||||
|
||||
<CommandBar.Content>
|
||||
<AutoSuggestBox
|
||||
Text="{Binding SearchText,Mode=TwoWay}"
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
<AppBarElementContainer>
|
||||
<ComboBox
|
||||
MinWidth="120"
|
||||
MinWidth="140"
|
||||
Height="36"
|
||||
Margin="2,6,3,6"
|
||||
DisplayMemberPath="Name"
|
||||
@@ -138,16 +138,8 @@
|
||||
<ScrollViewer Padding="0,0,16,0">
|
||||
<ItemsControl
|
||||
Margin="16,0,0,16"
|
||||
ItemsSource="{Binding Achievements}">
|
||||
<!--ContentThemeTransition here can make items blinking-->
|
||||
<!--<ItemsControl.Transitions>
|
||||
<ContentThemeTransition/>
|
||||
</ItemsControl.Transitions>-->
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<ItemsStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
ItemsSource="{Binding Achievements}"
|
||||
ItemsPanel="{StaticResource ItemsStackPanelTemplate}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid
|
||||
@@ -194,8 +186,7 @@
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="0"
|
||||
Text="{Binding Time}"
|
||||
Visibility="{Binding IsChecked,Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
/>
|
||||
Visibility="{Binding IsChecked,Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
<Image
|
||||
Grid.Column="1"
|
||||
Height="32"
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
xmlns:shca="using:Snap.Hutao.Control.Animation"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
d:DataContext="{d:DesignInstance shv:AnnouncementViewModel}"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<mxi:Interaction.Behaviors>
|
||||
@@ -28,7 +30,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding Announcement.List}"
|
||||
Padding="0"
|
||||
Margin="12,12,0,-12">
|
||||
Margin="16,16,0,-6">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
@@ -41,13 +43,9 @@
|
||||
SelectionMode="None"
|
||||
DesiredWidth="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding List}"
|
||||
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
|
||||
ItemsSource="{Binding List}"
|
||||
Margin="0,0,2,0">
|
||||
<cwucont:AdaptiveGridView.ItemContainerStyle>
|
||||
<Style TargetType="GridViewItem" BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
</cwucont:AdaptiveGridView.ItemContainerStyle>
|
||||
<cwucont:AdaptiveGridView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shcp="using:Snap.Hutao.Control.Panel"
|
||||
xmlns:shct="using:Snap.Hutao.Control.Text"
|
||||
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
||||
xmlns:shvcont="using:Snap.Hutao.View.Control"
|
||||
xmlns:shvconv="using:Snap.Hutao.View.Converter"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
@@ -45,7 +43,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<CommandBar
|
||||
DefaultLabelPosition="Right"
|
||||
Background="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
Background="{StaticResource CardBackgroundFillColorSecondaryBrush}">
|
||||
<CommandBar.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -54,7 +52,9 @@
|
||||
<ColumnDefinition Width="72"/>
|
||||
<ColumnDefinition Width="72"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shcp:PanelSelector Margin="6,6,0,0" x:Name="ItemsPanelSelector"/>
|
||||
<shcp:PanelSelector
|
||||
Margin="6,6,0,0"
|
||||
x:Name="ItemsPanelSelector"/>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="12,6,0,0">
|
||||
@@ -90,12 +90,12 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</CommandBar.Content>
|
||||
|
||||
<AppBarSeparator/>
|
||||
<AppBarButton
|
||||
Label="刷新"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Command="{Binding RefreshByUserGameRoleCommand}"/>
|
||||
|
||||
<AppBarButton
|
||||
Label="按UID查询"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
@@ -236,12 +236,8 @@
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Background="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding SelectedAvatar.Skills}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
ItemsSource="{Binding SelectedAvatar.Skills}"
|
||||
ItemsPanel="{StaticResource HorizontalStackPanelTemplate}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
@@ -450,12 +446,8 @@
|
||||
SelectionMode="None"
|
||||
HorizontalAlignment="Left"
|
||||
ItemsSource="{Binding SelectedAvatar.Reliquaries}"
|
||||
Margin="0,12,0,-12">
|
||||
<cwucont:AdaptiveGridView.ItemContainerStyle>
|
||||
<Style TargetType="GridViewItem" BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
</cwucont:AdaptiveGridView.ItemContainerStyle>
|
||||
Margin="0,12,0,-12"
|
||||
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
|
||||
53
src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml
Normal file
53
src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml
Normal file
@@ -0,0 +1,53 @@
|
||||
<shc:ScopedPage
|
||||
x:Class="Snap.Hutao.View.Page.DailyNotePage"
|
||||
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"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:sc="using:SettingsUI.Controls"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance shv:DailyNoteViewModel}"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<CommandBar
|
||||
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
|
||||
DefaultLabelPosition="Right">
|
||||
<AppBarButton Label="添加角色" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<AppBarButton Label="立即刷新" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<AppBarButton Label="通知设置" Icon="{shcm:FontIcon Glyph=}">
|
||||
<AppBarButton.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel>
|
||||
<RadioButtons ItemsSource="{Binding RefreshTimes}">
|
||||
<RadioButtons.Header>
|
||||
<TextBlock Text="刷新间隔时间" Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
</RadioButtons.Header>
|
||||
<RadioButtons.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Margin="0,0,0,0" Text="{Binding Name}"/>
|
||||
</DataTemplate>
|
||||
</RadioButtons.ItemTemplate>
|
||||
</RadioButtons>
|
||||
<sc:SettingsGroup Header="通知" Margin="0,-16,0,0">
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="提醒通知"
|
||||
Description="防止通知自动收入操作中心">
|
||||
<ToggleSwitch Margin="24,0,0,0" Style="{StaticResource ToggleSwitchSettingStyle}"/>
|
||||
</sc:Setting>
|
||||
</sc:SettingsGroup>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</AppBarButton.Flyout>
|
||||
</AppBarButton>
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
</shc:ScopedPage>
|
||||
22
src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml.cs
Normal file
22
src/Snap.Hutao/Snap.Hutao/View/Page/DailyNotePage.xaml.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.ViewModel;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺页面
|
||||
/// </summary>
|
||||
public sealed partial class DailyNotePage : ScopedPage
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的实时便笺页面
|
||||
/// </summary>
|
||||
public DailyNotePage()
|
||||
{
|
||||
InitializeWith<DailyNoteViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,7 @@
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<shc:ScopedPage.Resources>
|
||||
<Thickness x:Key="PivotHeaderItemMargin">8,0,8,0</Thickness>
|
||||
<Thickness x:Key="PivotItemMargin">0</Thickness>
|
||||
</shc:ScopedPage.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid Visibility="{Binding IsInitialized,Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Rectangle
|
||||
Height="48"
|
||||
VerticalAlignment="Top"
|
||||
@@ -34,7 +29,7 @@
|
||||
<ComboBox
|
||||
MinWidth="120"
|
||||
Height="36"
|
||||
Margin="16,6,12,6"
|
||||
Margin="16,6,0,6"
|
||||
DisplayMemberPath="Uid"
|
||||
SelectedItem="{Binding SelectedArchive,Mode=TwoWay}"
|
||||
ItemsSource="{Binding Archives}"/>
|
||||
@@ -44,14 +39,14 @@
|
||||
<AppBarButton Label="刷新" Icon="{shcm:FontIcon Glyph=}">
|
||||
<AppBarButton.Flyout>
|
||||
<MenuFlyout Placement="Bottom">
|
||||
<MenuFlyoutItem
|
||||
Text="从缓存刷新"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Command="{Binding RefreshByWebCacheCommand}"/>
|
||||
<MenuFlyoutItem
|
||||
Text="Stoken刷新"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Command="{Binding RefreshByStokenCommand}"/>
|
||||
<MenuFlyoutItem
|
||||
Text="从缓存刷新"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Command="{Binding RefreshByWebCacheCommand}"/>
|
||||
<MenuFlyoutItem
|
||||
Text="手动输入Url"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
|
||||
@@ -15,16 +15,11 @@
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance shv:HutaoDatabaseViewModel}"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<shc:ScopedPage.Resources>
|
||||
<Thickness x:Key="PivotHeaderItemMargin">8,0,8,0</Thickness>
|
||||
<Thickness x:Key="PivotItemMargin">0</Thickness>
|
||||
</shc:ScopedPage.Resources>
|
||||
|
||||
<Grid>
|
||||
<Pivot>
|
||||
<Pivot.RightHeader>
|
||||
@@ -55,25 +50,15 @@
|
||||
<GridView
|
||||
SelectionMode="None"
|
||||
ItemsSource="{Binding Avatars}"
|
||||
Margin="12,12,0,-12">
|
||||
<GridView.ItemContainerStyle>
|
||||
<Style TargetType="GridViewItem" BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
</GridView.ItemContainerStyle>
|
||||
Margin="16,16,6,-6"
|
||||
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<shvc:BottomTextControl Text="{Binding Rate}">
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
</shvc:BottomTextControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</GridView>
|
||||
@@ -95,25 +80,15 @@
|
||||
<GridView
|
||||
SelectionMode="None"
|
||||
ItemsSource="{Binding Avatars}"
|
||||
Margin="12,12,0,-12">
|
||||
<GridView.ItemContainerStyle>
|
||||
<Style TargetType="GridViewItem" BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
</GridView.ItemContainerStyle>
|
||||
Margin="16,16,6,-6"
|
||||
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<shvc:BottomTextControl Text="{Binding Rate}">
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
</shvc:BottomTextControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</GridView>
|
||||
@@ -128,7 +103,7 @@
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="12,0,12,0">
|
||||
<Grid Margin="16,0,16,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="48"/>
|
||||
<ColumnDefinition />
|
||||
@@ -152,6 +127,7 @@
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<ItemsControl
|
||||
Margin="0,0,0,8"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding AvatarConstellationInfos}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
@@ -164,7 +140,7 @@
|
||||
<Border
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Margin="12,0,12,12">
|
||||
Margin="16,0,16,8">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer Grid.Column="0" CanContentRenderOutsideBounds="True">
|
||||
<ScrollViewer Grid.RowSpan="2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MaxWidth="800"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Margin="32,0,24,24">
|
||||
<StackPanel Margin="16,-16,16,16">
|
||||
<sc:SettingsGroup Header="常规" Margin="0,0,0,0">
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
@@ -246,15 +246,21 @@
|
||||
</ScrollViewer>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="{StaticResource SystemControlAcrylicElementMediumHighBrush}">
|
||||
VerticalAlignment="Bottom">
|
||||
<Button
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
Command="{Binding LaunchCommand}"
|
||||
HorizontalAlignment="Right"
|
||||
Grid.Column="3"
|
||||
Margin="24"
|
||||
Width="138"
|
||||
Content="启动游戏"/>
|
||||
MinWidth="80"
|
||||
Width="100"
|
||||
Height="80">
|
||||
<StackPanel>
|
||||
<FontIcon Glyph="" FontSize="36"/>
|
||||
<TextBlock Margin="0,4,0,0" Text="启动游戏"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</shc:ScopedPage>
|
||||
@@ -23,17 +23,22 @@
|
||||
<ColumnDefinition MaxWidth="1000"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Margin="32,0,24,24">
|
||||
<StackPanel Margin="16,-16,24,16">
|
||||
<sc:SettingsGroup Header="关于 胡桃">
|
||||
<Grid Margin="0,4,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image
|
||||
Grid.Column="0"
|
||||
<Border
|
||||
Width="80"
|
||||
Source="ms-appx:///Assets/Square150x150Logo.scale-200.png"/>
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<Image Source="ms-appx:///Assets/Square150x150Logo.scale-200.png"/>
|
||||
</Border>
|
||||
|
||||
<Grid
|
||||
Margin="16,0,0,0"
|
||||
Grid.Column="1">
|
||||
@@ -56,10 +61,10 @@
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="反馈"
|
||||
Description="只处理在 Github 上反馈的问题">
|
||||
Description="Github 上反馈的问题会优先处理">
|
||||
<HyperlinkButton
|
||||
Content="前往反馈"
|
||||
NavigateUri="http://go.hut.ao/issue"/>
|
||||
NavigateUri="https://hut.ao/statements/bug-report.html"/>
|
||||
</sc:Setting>
|
||||
<sc:SettingExpander>
|
||||
<sc:SettingExpander.Header>
|
||||
@@ -75,7 +80,11 @@
|
||||
IsOpen="True"
|
||||
CornerRadius="0,0,4,4">
|
||||
<InfoBar.ActionButton>
|
||||
<Button HorizontalAlignment="Right" Width="1" Content="没用的按钮"/>
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
Width="1"
|
||||
Command="{Binding DebugExceptionCommand}"
|
||||
Content="没用的按钮"/>
|
||||
</InfoBar.ActionButton>
|
||||
</InfoBar>
|
||||
</sc:SettingExpander>
|
||||
@@ -105,7 +114,7 @@
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
</sc:SettingsGroup>
|
||||
|
||||
|
||||
<sc:SettingsGroup Header="测试功能">
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
@@ -134,7 +143,6 @@
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
</sc:SettingsGroup>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -60,10 +60,6 @@
|
||||
</shct:DescriptionTextBlock.Resources>
|
||||
</shct:DescriptionTextBlock>
|
||||
</DataTemplate>
|
||||
|
||||
<ItemsPanelTemplate x:Key="HorizontalStackPanelTemplate">
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Page.Resources>
|
||||
|
||||
<SplitView
|
||||
@@ -372,19 +368,13 @@
|
||||
ItemsSource="{Binding Selected.Collocation.Avatars}">
|
||||
<GridView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
<shvc:BottomTextControl
|
||||
ToolTipService.ToolTip="{Binding Name}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
Text="{Binding Rate}">
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
</shvc:BottomTextControl>
|
||||
</DataTemplate>
|
||||
</GridView.ItemTemplate>
|
||||
</GridView>
|
||||
@@ -397,23 +387,16 @@
|
||||
ItemsSource="{Binding Selected.Collocation.Weapons}">
|
||||
<GridView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
<shvc:BottomTextControl
|
||||
ToolTipService.ToolTip="{Binding Name}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
Text="{Binding Rate}">
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
</shvc:BottomTextControl>
|
||||
</DataTemplate>
|
||||
</GridView.ItemTemplate>
|
||||
</GridView>
|
||||
|
||||
<TextBlock Text="搭配圣遗物" Style="{StaticResource BaseTextBlockStyle}" Margin="16,0,0,0"/>
|
||||
<GridView
|
||||
Margin="16,16,0,0"
|
||||
@@ -423,30 +406,49 @@
|
||||
ItemsSource="{Binding Selected.Collocation.ReliquarySets}">
|
||||
<GridView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
<shvc:BottomTextControl
|
||||
ToolTipService.ToolTip="{Binding Name}"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}">
|
||||
<StackPanel>
|
||||
<Grid>
|
||||
<shvc:ItemIcon Quality="QUALITY_ORANGE"/>
|
||||
<ItemsControl
|
||||
Margin="0,0,16,0"
|
||||
HorizontalAlignment="Center"
|
||||
ItemsSource="{Binding Icons}"
|
||||
ItemsPanel="{StaticResource HorizontalStackPanelTemplate}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<shci:CachedImage Width="48" Margin="0,0,-16,0" Source="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Rate}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
Text="{Binding Rate}">
|
||||
<cwuc:SwitchPresenter Value="{Binding Icons.Count,Mode=OneWay}">
|
||||
<cwuc:Case IsDefault="True">
|
||||
<cwuc:Case.Value>
|
||||
<x:Int32>0</x:Int32>
|
||||
</cwuc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon Quality="QUALITY_ORANGE"/>
|
||||
</Grid>
|
||||
</cwuc:Case>
|
||||
<cwuc:Case>
|
||||
<cwuc:Case.Value>
|
||||
<x:Int32>1</x:Int32>
|
||||
</cwuc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon Quality="QUALITY_ORANGE"/>
|
||||
<shci:CachedImage Width="80" Margin="0,0,0,0" Source="{Binding Icons[0]}"/>
|
||||
</Grid>
|
||||
</cwuc:Case>
|
||||
<cwuc:Case>
|
||||
<cwuc:Case.Value>
|
||||
<x:Int32>2</x:Int32>
|
||||
</cwuc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon Quality="QUALITY_ORANGE"/>
|
||||
<shci:CachedImage
|
||||
Width="54"
|
||||
Margin="0,4,0,0"
|
||||
Source="{Binding Icons[0]}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"/>
|
||||
<shci:CachedImage
|
||||
Width="54"
|
||||
Margin="0,0,0,4"
|
||||
Source="{Binding Icons[1]}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"/>
|
||||
</Grid>
|
||||
</cwuc:Case>
|
||||
</cwuc:SwitchPresenter>
|
||||
</shvc:BottomTextControl>
|
||||
</DataTemplate>
|
||||
</GridView.ItemTemplate>
|
||||
</GridView>
|
||||
@@ -510,59 +512,29 @@
|
||||
Text="特殊料理"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<Border
|
||||
<shvc:BottomTextControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="16,16,0,16"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Text="{Binding Selected.FetterInfo.CookBonus.Name}">
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Selected.FetterInfo.CookBonus.Icon,Converter={StaticResource ItemIconConverter}}"
|
||||
Quality="{Binding Selected.FetterInfo.CookBonus.RankLevel}"/>
|
||||
<TextBlock
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
MaxWidth="80"
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CookBonus.Name}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</shvc:BottomTextControl>
|
||||
<TextBlock
|
||||
Margin="16,16,0,0"
|
||||
Text="原料理"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<Border
|
||||
<shvc:BottomTextControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="16,16,0,16"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Text="{Binding Selected.FetterInfo.CookBonus.OriginName}">
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Selected.FetterInfo.CookBonus.OriginIcon,Converter={StaticResource ItemIconConverter}}"
|
||||
Quality="{Binding Selected.FetterInfo.CookBonus.RankLevel}"/>
|
||||
<TextBlock
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxWidth="80"
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CookBonus.OriginName}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</shvc:BottomTextControl>
|
||||
<Rectangle
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
@@ -581,30 +553,14 @@
|
||||
ItemsPanel="{StaticResource HorizontalStackPanelTemplate}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
<shvc:BottomTextControl
|
||||
ToolTipService.ToolTip="{Binding Name}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="16,16,0,16"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon,Converter={StaticResource ItemIconConverter}}"
|
||||
Quality="{Binding RankLevel}"/>
|
||||
<TextBlock
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxWidth="80"
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Count}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
Text="{Binding Count}">
|
||||
<shvc:ItemIcon
|
||||
Icon="{Binding Icon,Converter={StaticResource ItemIconConverter}}"
|
||||
Quality="{Binding RankLevel}"/>
|
||||
</shvc:BottomTextControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -272,7 +272,7 @@ internal class AchievementViewModel
|
||||
[ThreadAccess(ThreadAccessState.MainThread)]
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
bool metaInitialized = await metadataService.InitializeAsync(CancellationToken).ConfigureAwait(false);
|
||||
bool metaInitialized = await metadataService.InitializeAsync().ConfigureAwait(false);
|
||||
|
||||
if (metaInitialized)
|
||||
{
|
||||
|
||||
@@ -83,7 +83,7 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
if (user.SelectedUserGameRole is UserGameRole role)
|
||||
{
|
||||
return RefreshCoreAsync((PlayerUid)role, RefreshOption.DatabaseOnly);
|
||||
return RefreshCoreAsync((PlayerUid)role, RefreshOption.DatabaseOnly, CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
if (user.SelectedUserGameRole is UserGameRole role)
|
||||
{
|
||||
return RefreshCoreAsync((PlayerUid)role, RefreshOption.Standard);
|
||||
return RefreshCoreAsync((PlayerUid)role, RefreshOption.Standard, CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,31 +110,37 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
await RefreshCoreAsync(uid, RefreshOption.RequestFromAPI).ConfigureAwait(false);
|
||||
await RefreshCoreAsync(uid, RefreshOption.RequestFromAPI, CancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshCoreAsync(PlayerUid uid, RefreshOption option)
|
||||
private async Task RefreshCoreAsync(PlayerUid uid, RefreshOption option, CancellationToken token)
|
||||
{
|
||||
(RefreshResult result, Summary? summary) = await avatarInfoService.GetSummaryAsync(uid, option).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
(RefreshResult result, Summary? summary) = await avatarInfoService.GetSummaryAsync(uid, option, token).ConfigureAwait(false);
|
||||
|
||||
if (result == RefreshResult.Ok)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
Summary = summary;
|
||||
SelectedAvatar = Summary?.Avatars.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (result)
|
||||
if (result == RefreshResult.Ok)
|
||||
{
|
||||
case RefreshResult.APIUnavailable:
|
||||
infoBarService.Warning("角色信息服务当前不可用");
|
||||
break;
|
||||
case RefreshResult.ShowcaseNotOpen:
|
||||
infoBarService.Warning("角色橱窗尚未开启,请前往游戏操作后重试");
|
||||
break;
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
Summary = summary;
|
||||
SelectedAvatar = Summary?.Avatars.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case RefreshResult.APIUnavailable:
|
||||
infoBarService.Warning("角色信息服务当前不可用");
|
||||
break;
|
||||
case RefreshResult.ShowcaseNotOpen:
|
||||
infoBarService.Warning("角色橱窗尚未开启,请前往游戏操作后重试");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs
Normal file
32
src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Model;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺视图模型
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
private readonly List<NamedValue<int>> refreshTimes = new()
|
||||
{
|
||||
new("4 分钟 | 0.5 树脂", 240),
|
||||
new("8 分钟 | 1 树脂", 480),
|
||||
new("30 分钟 | 3.75 树脂", 1800),
|
||||
new("40 分钟 | 5 树脂", 2400),
|
||||
new("60 分钟 | 7.5 树脂", 3600),
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 刷新时间
|
||||
/// </summary>
|
||||
public List<NamedValue<int>> RefreshTimes { get => refreshTimes; }
|
||||
}
|
||||
@@ -66,15 +66,20 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
|
||||
{
|
||||
HomaClient homaClient = Ioc.Default.GetRequiredService<HomaClient>();
|
||||
IUserService userService = Ioc.Default.GetRequiredService<IUserService>();
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
|
||||
if (userService.Current is Model.Binding.User user)
|
||||
{
|
||||
if (user.SelectedUserGameRole == null)
|
||||
{
|
||||
infoBarService.Warning("尚未选择角色");
|
||||
}
|
||||
|
||||
SimpleRecord record = await homaClient.GetPlayerRecordAsync(user).ConfigureAwait(false);
|
||||
Web.Response.Response<string>? response = await homaClient.UploadRecordAsync(record).ConfigureAwait(false);
|
||||
|
||||
if (response != null && response.IsOk())
|
||||
{
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
infoBarService.Success(response.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
|
||||
private GachaStatistics? statistics;
|
||||
private bool isAggressiveRefresh;
|
||||
private HistoryWish? selectedHistoryWish;
|
||||
private bool isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的祈愿记录视图模型
|
||||
@@ -112,6 +113,11 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
|
||||
/// </summary>
|
||||
public bool IsAggressiveRefresh { get => isAggressiveRefresh; set => SetProperty(ref isAggressiveRefresh, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化是否完成
|
||||
/// </summary>
|
||||
public bool IsInitialized { get => isInitialized; set => SetProperty(ref isInitialized, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 页面加载命令
|
||||
/// </summary>
|
||||
@@ -183,6 +189,9 @@ internal class GachaLogViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
infoBarService.Information("请刷新或导入祈愿记录");
|
||||
}
|
||||
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
IsInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,11 @@ using Snap.Hutao.Model.Binding.LaunchGame;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
@@ -159,9 +161,9 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
(bool isOk, string gamePath) = await gameService.GetGamePathAsync().ConfigureAwait(false);
|
||||
bool gameExists = File.Exists(gameService.GetGamePathSkipLocator());
|
||||
|
||||
if (isOk)
|
||||
if (gameExists)
|
||||
{
|
||||
MultiChannel multi = gameService.GetMultiChannel();
|
||||
SelectedScheme = KnownSchemes.FirstOrDefault(s => s.Channel == multi.Channel && s.SubChannel == multi.SubChannel);
|
||||
@@ -170,6 +172,11 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
// Sync from Settings
|
||||
RetiveSetting();
|
||||
}
|
||||
else
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IInfoBarService>().Warning("游戏路径不正确,前往设置更改游戏路径。");
|
||||
await Ioc.Default.GetRequiredService<INavigationService>().NavigateAsync<View.Page.SettingPage>(INavigationAwaiter.Default, true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void RetiveSetting()
|
||||
@@ -209,6 +216,8 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
|
||||
if (gameService.IsGameRunning())
|
||||
{
|
||||
return;
|
||||
@@ -216,7 +225,14 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
if (SelectedScheme != null)
|
||||
{
|
||||
gameService.SetMultiChannel(SelectedScheme);
|
||||
try
|
||||
{
|
||||
gameService.SetMultiChannel(SelectedScheme);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
infoBarService.Warning("切换服务器失败,保存配置文件时发生异常\n请以管理员模式启动胡桃。");
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectedGameAccount != null)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
@@ -46,11 +47,13 @@ internal class SettingViewModel : ObservableObject
|
||||
GamePath = gameService.GetGamePathSkipLocator();
|
||||
|
||||
SetGamePathCommand = asyncRelayCommandFactory.Create(SetGamePathAsync);
|
||||
DebugExceptionCommand = new RelayCommand(DebugThrowException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 版本
|
||||
/// </summary>
|
||||
[SuppressMessage("", "CA1822")]
|
||||
public string AppVersion
|
||||
{
|
||||
get => Core.CoreEnvironment.Version.ToString();
|
||||
@@ -90,6 +93,11 @@ internal class SettingViewModel : ObservableObject
|
||||
/// </summary>
|
||||
public ICommand SetGamePathCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 调试异常命令
|
||||
/// </summary>
|
||||
public ICommand DebugExceptionCommand { get; }
|
||||
|
||||
private async Task SetGamePathAsync()
|
||||
{
|
||||
IGameLocator locator = Ioc.Default.GetRequiredService<IEnumerable<IGameLocator>>()
|
||||
@@ -103,4 +111,11 @@ internal class SettingViewModel : ObservableObject
|
||||
GamePath = path;
|
||||
}
|
||||
}
|
||||
|
||||
private void DebugThrowException()
|
||||
{
|
||||
#if DEBUG
|
||||
throw new InvalidOperationException("测试用异常");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -161,6 +161,8 @@ internal class WikiAvatarViewModel : ObservableObject
|
||||
await CombineWithAvatarCollocationsAsync(sorted).ConfigureAwait(false);
|
||||
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
|
||||
// RPC_E_WRONG_THREAD ?
|
||||
Avatars = new AdvancedCollectionView(sorted, true);
|
||||
Selected = Avatars.Cast<Avatar>().FirstOrDefault();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Snap.Hutao.Web.Hoyolab;
|
||||
/// </summary>
|
||||
public struct PlayerUid
|
||||
{
|
||||
[SuppressMessage("", "CA1805")]
|
||||
[SuppressMessage("", "IDE0079")]
|
||||
private string? region = default;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -202,4 +202,4 @@ internal class HomaClient
|
||||
{
|
||||
return httpClient.TryCatchPostAsJsonAsync<SimpleRecord, Response<string>>($"{HutaoAPI}/Record/Upload", playerRecord, options, logger, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs
Normal file
52
src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hutao.Log;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃日志客户端
|
||||
/// </summary>
|
||||
[HttpClient(HttpClientConfigration.Default)]
|
||||
internal class HomaClient2
|
||||
{
|
||||
private const string HutaoAPI = "https://homa.snapgenshin.com";
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly JsonSerializerOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃日志客户端
|
||||
/// </summary>
|
||||
/// <param name="httpClient">Http客户端</param>
|
||||
/// <param name="options">Json序列化选项</param>
|
||||
public HomaClient2(HttpClient httpClient, JsonSerializerOptions options)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传日志
|
||||
/// </summary>
|
||||
/// <param name="exception">异常</param>
|
||||
/// <returns>任务</returns>
|
||||
public async Task<string?> UploadLogAsync(Exception exception)
|
||||
{
|
||||
HutaoLog log = new()
|
||||
{
|
||||
Id = Core.CoreEnvironment.HutaoDeviceId,
|
||||
Time = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
|
||||
Info = exception.ToString(),
|
||||
};
|
||||
|
||||
Response<string>? a = await httpClient
|
||||
.TryCatchPostAsJsonAsync<HutaoLog, Response<string>>($"{HutaoAPI}/HutaoLog/Upload", log, options)
|
||||
.ConfigureAwait(false);
|
||||
return a?.Data;
|
||||
}
|
||||
}
|
||||
25
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HutaoLog.cs
Normal file
25
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Log/HutaoLog.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Log;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃日志
|
||||
/// </summary>
|
||||
public class HutaoLog
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备Id
|
||||
/// </summary>
|
||||
public string Id { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 崩溃时间
|
||||
/// </summary>
|
||||
public long Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string Info { get; set; } = default!;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public class SimpleRank
|
||||
/// 构造一个新的数值
|
||||
/// </summary>
|
||||
/// <param name="rank">排行</param>
|
||||
public SimpleRank(Rank rank)
|
||||
private SimpleRank(Rank rank)
|
||||
{
|
||||
AvatarId = rank.AvatarId;
|
||||
Value = rank.Value;
|
||||
@@ -29,4 +29,19 @@ public class SimpleRank
|
||||
/// 值
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的简单数值
|
||||
/// </summary>
|
||||
/// <param name="rank">排行</param>
|
||||
/// <returns>新的简单数值</returns>
|
||||
public static SimpleRank? FromRank(Rank? rank)
|
||||
{
|
||||
if (rank == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SimpleRank(rank);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ public class SimpleSpiralAbyss
|
||||
public SimpleSpiralAbyss(SpiralAbyss spiralAbyss)
|
||||
{
|
||||
ScheduleId = spiralAbyss.ScheduleId;
|
||||
Damage = new(spiralAbyss.DamageRank.Single());
|
||||
TakeDamage = new(spiralAbyss.TakeDamageRank.Single());
|
||||
Damage = SimpleRank.FromRank(spiralAbyss.DamageRank.SingleOrDefault());
|
||||
TakeDamage = SimpleRank.FromRank(spiralAbyss.TakeDamageRank.SingleOrDefault());
|
||||
Floors = spiralAbyss.Floors.Select(f => new SimpleFloor(f));
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ public class SimpleSpiralAbyss
|
||||
/// <summary>
|
||||
/// 造成伤害
|
||||
/// </summary>
|
||||
public SimpleRank Damage { get; set; } = default!;
|
||||
public SimpleRank? Damage { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 受到伤害
|
||||
/// </summary>
|
||||
public SimpleRank TakeDamage { get; set; } = default!;
|
||||
public SimpleRank? TakeDamage { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 层
|
||||
|
||||
@@ -68,6 +68,11 @@ public enum KnownReturnCode : int
|
||||
/// </summary>
|
||||
NotDefined = 7,
|
||||
|
||||
/// <summary>
|
||||
/// 账号有风险
|
||||
/// </summary>
|
||||
CODE1034 = 1034,
|
||||
|
||||
/// <summary>
|
||||
/// 数据未公开
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user