Compare commits

..

6 Commits

Author SHA1 Message Date
DismissedLight
bb2665b75e Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-01-16 14:27:45 +08:00
DismissedLight
d22ac39c1d fix dupe download items [skip ci] 2023-01-16 14:27:31 +08:00
Masterain
a312603d61 Update azure-pipelines.yml for Azure Pipelines 2023-01-15 22:22:34 -08:00
DismissedLight
0732ea0e06 replace font 2023-01-16 14:10:28 +08:00
Masterain
e4d2b3055c Update azure-pipelines.yml for Azure Pipelines
[skip ci]
2023-01-14 17:28:43 -08:00
Masterain
5668931230 Update PublishDistribution.yml
[skip ci]
2023-01-14 17:08:50 -08:00
59 changed files with 731 additions and 209 deletions

View File

@@ -39,3 +39,13 @@ jobs:
EOF EOF
rclone copy ./release-download/* dgpODCN:/releases/ rclone copy ./release-download/* dgpODCN:/releases/
# Purge Patch System Cache
- name: Purge Patch
env:
PATCH_HOSTS: ${{ secrets.PATCH_HOSTS }}
PURGE_TOKEN: ${{ secrets.PURGE_TOKEN }}
PURGE_URL: ${{ secrets.PURGE_URL }}
run: |
sudo echo "$PATCH_HOSTS" | sudo tee -a /etc/hosts
curl --header "Authorization: token $PURGE_TOKEN" $PURGE_URL

View File

@@ -1,12 +1,13 @@
# [Snap.Hutao](https://hut.ao) # [Snap.Hutao](https://hut.ao)
![](https://repository-images.githubusercontent.com/482734649/5f8cf574-2ef0-43e9-aa8d-6cf094b54dd9)
> 唷,找本堂主有何贵干呀? > 唷,找本堂主有何贵干呀?
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg) ![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)
# 特别感谢 # 特别感谢
### 原神组织与个人
* [HolographicHat](https://github.com/HolographicHat) * [HolographicHat](https://github.com/HolographicHat)
* [UIGF organization](https://uigf.org) * [UIGF organization](https://uigf.org)

View File

@@ -91,7 +91,8 @@ steps:
"Package/Identity/@Publisher": "CN=DGP Studio CI", "Package/Identity/@Publisher": "CN=DGP Studio CI",
"Package/Identity/@Version": "$(build_date).$(rev_number)", "Package/Identity/@Version": "$(build_date).$(rev_number)",
"Package/Properties/DisplayName": "胡桃 Alpha", "Package/Properties/DisplayName": "胡桃 Alpha",
"Package/Properties/PublisherDisplayName":"DGP Studio CI" "Package/Properties/PublisherDisplayName":"DGP Studio CI",
"Package/Applications/Application/uap:VisualElements/@DisplayName": "胡桃 Alpha"
} }
- task: CmdLine@2 - task: CmdLine@2
@@ -167,12 +168,13 @@ steps:
changeLogType: 'commitBased' changeLogType: 'commitBased'
- task: DownloadSecureFile@1 - task: DownloadSecureFile@1
name: cerFile name: RcloneConfigFile
displayName: Download Rclone Config displayName: Download Rclone Config
inputs: inputs:
secureFile: 'rclone.conf' secureFile: 'rclone.conf'
- task: rclone@1 - task: rclone@1
displayName: Upload CI via Rclone
inputs: inputs:
arguments: 'copy $(Build.ArtifactStagingDirectory)/* downloadDGPCN:/releases/Alpha/' arguments: 'copy $(Build.ArtifactStagingDirectory)/* downloadDGPCN:/releases/Alpha/'
configPath: '$(cerFile.secureFilePath)/rclone.conf' configPath: '$(RcloneConfigFile.secureFilePath)/rclone.conf'

View File

@@ -11,6 +11,7 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<muxc:XamlControlsResources/> <muxc:XamlControlsResources/>
<ResourceDictionary Source="ms-appx:///SettingsUI/Themes/Generic.xaml"/> <ResourceDictionary Source="ms-appx:///SettingsUI/Themes/Generic.xaml"/>
<ResourceDictionary Source="Control/Theme/FontStyle.xaml"/>
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries> <ResourceDictionary.ThemeDictionaries>
@@ -27,8 +28,6 @@
<StaticResource x:Key="WindowCaptionBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/> <StaticResource x:Key="WindowCaptionBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
<!-- Page Transparent Background --> <!-- Page Transparent Background -->
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/> <StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/>
<!-- IconFont -->
<FontFamily x:Key="SymbolThemeFontFamily">ms-appx:///Resource/Font/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
<!-- InfoBar Resource --> <!-- InfoBar Resource -->
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness> <Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness> <Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
@@ -90,6 +89,7 @@
<shvc:Int32ToVisibilityConverter x:Key="Int32ToVisibilityConverter"/> <shvc:Int32ToVisibilityConverter x:Key="Int32ToVisibilityConverter"/>
<shvc:Int32ToVisibilityRevertConverter x:Key="Int32ToVisibilityRevertConverter"/> <shvc:Int32ToVisibilityRevertConverter x:Key="Int32ToVisibilityRevertConverter"/>
<!-- Styles --> <!-- Styles -->
<Style <Style
x:Key="LargeGridViewItemStyle" x:Key="LargeGridViewItemStyle"
BasedOn="{StaticResource DefaultGridViewItemStyle}" BasedOn="{StaticResource DefaultGridViewItemStyle}"

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.WinUI.Notifications; using CommunityToolkit.WinUI.Notifications;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.Windows.AppLifecycle; using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core; using Snap.Hutao.Core;
using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.ExceptionService;

View File

@@ -0,0 +1,210 @@
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
<!-- Licensed under the MIT License. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wsc="using:WinUICommunity.SettingsUI.Controls">
<FontFamily x:Key="MiSans">ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
<FontFamily x:Key="CascadiaMonoAndMiSans">ms-appx:///Resource/Font/CascadiaMono.ttf#Cascadia Mono, ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
<StaticResource x:Key="PivotHeaderItemFontFamily" ResourceKey="MiSans"/>
<StaticResource x:Key="ContentControlThemeFontFamily" ResourceKey="MiSans"/>
<Style BasedOn="{StaticResource BodyTextBlockStyle}" TargetType="TextBlock"/>
<Style x:Key="BaseTextBlockStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
<Setter Property="FontSize" Value="{StaticResource BodyTextBlockFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="LineStackingStrategy" Value="MaxHeight"/>
<Setter Property="TextLineBounds" Value="Full"/>
</Style>
<Style
x:Key="HeaderTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="46"/>
<Setter Property="FontWeight" Value="Light"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="SubheaderTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="34"/>
<Setter Property="FontWeight" Value="Light"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="TitleTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource TitleTextBlockFontSize}"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="SubtitleTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource SubtitleTextBlockFontSize}"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="BodyTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style
x:Key="CaptionTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource CaptionTextBlockFontSize}"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style
x:Key="BodyStrongTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource BodyStrongTextBlockFontSize}"/>
</Style>
<Style
x:Key="TitleLargeTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource TitleLargeTextBlockFontSize}"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style
x:Key="DisplayTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource DisplayTextBlockFontSize}"/>
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>
<Style BasedOn="{StaticResource DefaultMenuFlyoutItemStyle}" TargetType="MenuFlyoutItem">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<Style BasedOn="{StaticResource DefaultMenuFlyoutSubItemStyle}" TargetType="MenuFlyoutSubItem">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<Style BasedOn="{StaticResource DefaultScrollViewerStyle}" TargetType="ScrollViewer">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<Style TargetType="InfoBar">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<Style BasedOn="{StaticResource DefaultSettingStyle}" TargetType="wsc:Setting"/>
<Style x:Key="DefaultSettingStyle" TargetType="wsc:Setting">
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="Background" Value="{ThemeResource CardBackgroundBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource CardBorderThickness}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Margin" Value="0,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="wsc:Setting">
<Grid
x:Name="RootGrid"
MinHeight="48"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<!-- Icon -->
<ColumnDefinition Width="*"/>
<!-- Header and subtitle -->
<ColumnDefinition Width="Auto"/>
<!-- Action control -->
</Grid.ColumnDefinitions>
<ContentPresenter
x:Name="IconPresenter"
MaxWidth="20"
Margin="2,0,18,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Icon}"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{ThemeResource CardPrimaryForegroundBrush}"
IsTextScaleFactorEnabled="False"/>
<StackPanel
Grid.Column="1"
Margin="0,0,16,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<TextBlock
x:Name="HeaderPresenter"
VerticalAlignment="Center"
FontFamily="{StaticResource MiSans}"
Foreground="{ThemeResource CardPrimaryForegroundBrush}"
Text="{TemplateBinding Header}"/>
<ContentPresenter
x:Name="DescriptionPresenter"
Content="{TemplateBinding Description}"
FontFamily="{StaticResource MiSans}"
FontSize="{StaticResource SecondaryTextFontSize}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="WrapWholeWords">
<ContentPresenter.Resources>
<Style BasedOn="{StaticResource CaptionTextBlockStyle}" TargetType="TextBlock">
<Style.Setters>
<Setter Property="TextWrapping" Value="WrapWholeWords"/>
</Style.Setters>
</Style>
<Style BasedOn="{StaticResource TextButtonStyle}" TargetType="HyperlinkButton">
<Style.Setters>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="0,0,0,0"/>
</Style.Setters>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
<ContentPresenter
x:Name="ContentPresenter"
Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{TemplateBinding ActionContent}"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="HeaderPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
<Setter Target="DescriptionPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
<Setter Target="IconPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
<Setter Target="ContentPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Logging;
using System.Collections.Concurrent;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO; using System.IO;
using System.Net; using System.Net;
@@ -24,7 +25,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
{ {
private const string CacheFolderName = nameof(ImageCache); private const string CacheFolderName = nameof(ImageCache);
private static readonly ImmutableDictionary<int, TimeSpan> RetryCountToDelay = new Dictionary<int, TimeSpan>() private static readonly Dictionary<int, TimeSpan> RetryCountToDelay = new Dictionary<int, TimeSpan>()
{ {
[0] = TimeSpan.FromSeconds(4), [0] = TimeSpan.FromSeconds(4),
[1] = TimeSpan.FromSeconds(16), [1] = TimeSpan.FromSeconds(16),
@@ -32,13 +33,13 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
[3] = TimeSpan.FromSeconds(4), [3] = TimeSpan.FromSeconds(4),
[4] = TimeSpan.FromSeconds(16), [4] = TimeSpan.FromSeconds(16),
[5] = TimeSpan.FromSeconds(64), [5] = TimeSpan.FromSeconds(64),
}.ToImmutableDictionary(); };
private readonly ILogger logger; private readonly ILogger logger;
// violate di rule
private readonly HttpClient httpClient; private readonly HttpClient httpClient;
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
private string? baseFolder; private string? baseFolder;
private string? cacheFolder; private string? cacheFolder;
@@ -100,11 +101,30 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
/// <inheritdoc/> /// <inheritdoc/>
public async Task<string> GetFileFromCacheAsync(Uri uri) public async Task<string> GetFileFromCacheAsync(Uri uri)
{ {
string filePath = Path.Combine(GetCacheFolder(), GetCacheFileName(uri)); string fileName = GetCacheFileName(uri);
string filePath = Path.Combine(GetCacheFolder(), fileName);
if (!File.Exists(filePath) || new FileInfo(filePath).Length == 0) if (!File.Exists(filePath) || new FileInfo(filePath).Length == 0)
{ {
await DownloadFileAsync(uri, filePath).ConfigureAwait(false); TaskCompletionSource taskCompletionSource = new();
try
{
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
{
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
}
else
{
if (concurrentTasks.TryGetValue(fileName, out Task? task))
{
await task.ConfigureAwait(false);
}
}
}
finally
{
taskCompletionSource.TrySetResult();
}
} }
return filePath; return filePath;
@@ -191,7 +211,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
if (retryCount == 3) if (retryCount == 3)
{ {
uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao, }.Uri; uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao }.Uri;
} }
} }
} }

View File

@@ -10,7 +10,8 @@ namespace Snap.Hutao.Core.DependencyInjection;
/// </summary> /// </summary>
public static class ServiceScopeExtension public static class ServiceScopeExtension
{ {
private static IServiceScope? scopeReference; // Allow GC to Collect the IServiceScope
private static readonly WeakReference<IServiceScope> ScopeReference = new(null!);
/// <summary> /// <summary>
/// 追踪服务范围 /// 追踪服务范围
@@ -19,7 +20,7 @@ public static class ServiceScopeExtension
public static void Track(this IServiceScope scope) public static void Track(this IServiceScope scope)
{ {
DisposeLast(); DisposeLast();
scopeReference = scope; ScopeReference.SetTarget(scope);
} }
/// <summary> /// <summary>
@@ -27,6 +28,9 @@ public static class ServiceScopeExtension
/// </summary> /// </summary>
public static void DisposeLast() public static void DisposeLast()
{ {
scopeReference?.Dispose(); if (ScopeReference.TryGetTarget(out IServiceScope? scope))
{
scope.Dispose();
}
} }
} }

View File

@@ -45,4 +45,4 @@ internal class ExceptionRecorder
{ {
logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message); logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message);
} }
} }

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.ExceptionService;
/// <summary>
/// 用户数据损坏异常
/// </summary>
internal class UserdataCorruptedException : Exception
{
/// <summary>
/// 构造一个新的用户数据损坏异常
/// </summary>
/// <param name="message">消息</param>
/// <param name="innerException">内部错误</param>
public UserdataCorruptedException(string message, Exception innerException)
: base($"用户数据已损坏: {message}", innerException)
{
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.Win32.TaskScheduler; using Microsoft.Win32.TaskScheduler;
using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SchedulerTask = Microsoft.Win32.TaskScheduler.Task; using SchedulerTask = Microsoft.Win32.TaskScheduler.Task;
@@ -23,6 +24,7 @@ internal static class ScheduleTaskHelper
{ {
try try
{ {
// TODO: 似乎可以不删除任务,直接注册已经包含了更新功能
SchedulerTask? targetTask = TaskService.Instance.GetTask(DailyNoteRefreshTaskName); SchedulerTask? targetTask = TaskService.Instance.GetTask(DailyNoteRefreshTaskName);
if (targetTask != null) if (targetTask != null)
{ {
@@ -36,12 +38,9 @@ internal static class ScheduleTaskHelper
TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task); TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task);
return true; return true;
} }
catch (UnauthorizedAccessException) catch (Exception ex)
{
return false;
}
catch (COMException)
{ {
_ = ex;
return false; return false;
} }
} }

View File

@@ -32,4 +32,9 @@ internal static class SettingKeys
/// 静态资源合约V2 成就图标与物品图标 /// 静态资源合约V2 成就图标与物品图标
/// </summary> /// </summary>
public const string StaticResourceV2Contract = "StaticResourceV2Contract"; public const string StaticResourceV2Contract = "StaticResourceV2Contract";
/// <summary>
/// 静态资源合约V3 刷新 Skill Talent
/// </summary>
public const string StaticResourceV3Contract = "StaticResourceV3Contract";
} }

View File

@@ -39,6 +39,13 @@ internal static class ThreadHelper
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InvokeOnMainThread(Action action) public static void InvokeOnMainThread(Action action)
{ {
Program.DispatcherQueue!.Invoke(action); if (Program.DispatcherQueue!.HasThreadAccess)
{
action();
}
else
{
Program.DispatcherQueue.Invoke(action);
}
} }
} }

View File

@@ -9,7 +9,7 @@ namespace Snap.Hutao.Model.Intrinsic;
/// <summary> /// <summary>
/// 不可变的原生枚举 /// 不可变的原生枚举
/// </summary> /// </summary>
public static class ImmutableIntrinsics public static class IntrinsicImmutables
{ {
/// <summary> /// <summary>
/// 所属地区 /// 所属地区

View File

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

View File

@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics; using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Logging;
using Snap.Hutao.Extension; using Snap.Hutao.Extension;
using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Model.Entity.Database;
@@ -114,7 +115,17 @@ internal class AchievementService : IAchievementService
List<BindingAchievement> results = new(); List<BindingAchievement> results = new();
foreach (MetadataAchievement meta in metadata) foreach (MetadataAchievement meta in metadata)
{ {
EntityAchievement entity = entities.SingleOrDefault(e => e.Id == meta.Id) ?? EntityAchievement.Create(archiveId, meta.Id); EntityAchievement? entity;
try
{
entity = entities.SingleOrDefault(e => e.Id == meta.Id);
}
catch (InvalidOperationException ex)
{
throw new UserdataCorruptedException("单个成就存档内发现多个相同的成就 Id", ex);
}
entity ??= EntityAchievement.Create(archiveId, meta.Id);
results.Add(new(meta, entity)); results.Add(new(meta, entity));
} }

View File

@@ -22,7 +22,6 @@ namespace Snap.Hutao.Service.AvatarInfo;
[Injection(InjectAs.Scoped, typeof(IAvatarInfoService))] [Injection(InjectAs.Scoped, typeof(IAvatarInfoService))]
internal class AvatarInfoService : IAvatarInfoService internal class AvatarInfoService : IAvatarInfoService
{ {
private readonly AppDbContext appDbContext;
private readonly ISummaryFactory summaryFactory; private readonly ISummaryFactory summaryFactory;
private readonly IMetadataService metadataService; private readonly IMetadataService metadataService;
private readonly ILogger<AvatarInfoService> logger; private readonly ILogger<AvatarInfoService> logger;
@@ -42,7 +41,6 @@ internal class AvatarInfoService : IAvatarInfoService
ISummaryFactory summaryFactory, ISummaryFactory summaryFactory,
ILogger<AvatarInfoService> logger) ILogger<AvatarInfoService> logger)
{ {
this.appDbContext = appDbContext;
this.metadataService = metadataService; this.metadataService = metadataService;
this.summaryFactory = summaryFactory; this.summaryFactory = summaryFactory;
this.logger = logger; this.logger = logger;

View File

@@ -43,7 +43,7 @@ internal class SummaryAvatarFactory
ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull()); ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull());
MetadataAvatar avatar = metadataContext.IdAvatarMap[avatarInfo.AvatarId]; MetadataAvatar avatar = metadataContext.IdAvatarMap[avatarInfo.AvatarId];
return new() PropertyAvatar propertyAvatar = new()
{ {
Id = avatar.Id, Id = avatar.Id,
Name = avatar.Name, Name = avatar.Name,
@@ -52,8 +52,6 @@ internal class SummaryAvatarFactory
NameCard = AvatarNameCardPicConverter.AvatarToUri(avatar), NameCard = AvatarNameCardPicConverter.AvatarToUri(avatar),
Quality = avatar.Quality, Quality = avatar.Quality,
Element = ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore), Element = ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore),
Level = $"Lv.{avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value}",
LevelNumber = int.Parse(avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value ?? string.Empty),
FetterLevel = avatarInfo.FetterInfo.ExpLevel, FetterLevel = avatarInfo.FetterInfo.ExpLevel,
Weapon = reliquaryAndWeapon.Weapon, Weapon = reliquaryAndWeapon.Weapon,
Reliquaries = reliquaryAndWeapon.Reliquaries, Reliquaries = reliquaryAndWeapon.Reliquaries,
@@ -62,7 +60,11 @@ internal class SummaryAvatarFactory
Properties = SummaryHelper.CreateAvatarProperties(avatarInfo.FightPropMap), Properties = SummaryHelper.CreateAvatarProperties(avatarInfo.FightPropMap),
Score = reliquaryAndWeapon.Reliquaries.Sum(r => r.Score).ToString("F2"), Score = reliquaryAndWeapon.Reliquaries.Sum(r => r.Score).ToString("F2"),
CritScore = $"{SummaryHelper.ScoreCrit(avatarInfo.FightPropMap):F2}", CritScore = $"{SummaryHelper.ScoreCrit(avatarInfo.FightPropMap):F2}",
LevelNumber = int.Parse(avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].Value ?? string.Empty),
}; };
propertyAvatar.Level = $"Lv.{propertyAvatar.LevelNumber}";
return propertyAvatar;
} }
private ReliquaryAndWeapon ProcessEquip(List<Equip> equipments) private ReliquaryAndWeapon ProcessEquip(List<Equip> equipments)
@@ -85,7 +87,7 @@ internal class SummaryAvatarFactory
} }
} }
return new(reliquaryList, weapon!); return new(reliquaryList, weapon);
} }
private PropertyWeapon CreateWeapon(Equip equip) private PropertyWeapon CreateWeapon(Equip equip)

View File

@@ -47,6 +47,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
{ {
ThreadHelper.InvokeOnMainThread(() => ThreadHelper.InvokeOnMainThread(() =>
{ {
// Database items have been deleted by cascade deleting.
entries?.RemoveWhere(n => n.UserId == message.RemovedUserId); entries?.RemoveWhere(n => n.UserId == message.RemovedUserId);
}); });
} }
@@ -151,6 +152,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
using (IServiceScope scope = scopeFactory.CreateScope()) using (IServiceScope scope = scopeFactory.CreateScope())
{ {
// DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s)
scope.ServiceProvider.GetRequiredService<AppDbContext>().DailyNotes.RemoveAndSave(entry); scope.ServiceProvider.GetRequiredService<AppDbContext>().DailyNotes.RemoveAndSave(entry);
} }
} }

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics; using Snap.Hutao.Core.Diagnostics;
@@ -296,6 +297,7 @@ internal class GachaLogService : IGachaLogService
break; break;
} }
token.ThrowIfCancellationRequested();
SaveGachaItems(itemsToAdd, isLazy, archive, configration.EndId); SaveGachaItems(itemsToAdd, isLazy, archive, configration.EndId);
await RandomDelayAsync(token).ConfigureAwait(false); await RandomDelayAsync(token).ConfigureAwait(false);
} }
@@ -327,14 +329,21 @@ internal class GachaLogService : IGachaLogService
if (archive != null) if (archive != null)
{ {
// TODO: replace with MaxBy try
// https://github.com/dotnet/efcore/issues/25566 {
// .MaxBy(i => i.Id); // TODO: replace with MaxBy
item = appDbContext.GachaItems // https://github.com/dotnet/efcore/issues/25566
.Where(i => i.ArchiveId == archive.InnerId) // .MaxBy(i => i.Id);
.Where(i => i.QueryType == configType) item = appDbContext.GachaItems
.OrderByDescending(i => i.Id) .Where(i => i.ArchiveId == archive.InnerId)
.FirstOrDefault(); .Where(i => i.QueryType == configType)
.OrderByDescending(i => i.Id)
.FirstOrDefault();
}
catch (SqliteException ex)
{
throw new Core.ExceptionService.UserdataCorruptedException("无法获取祈愿记录 End Id", ex);
}
} }
return item?.Id ?? 0L; return item?.Id ?? 0L;
@@ -369,11 +378,12 @@ internal class GachaLogService : IGachaLogService
private INameQuality GetNameQualityByItemId(int id) private INameQuality GetNameQualityByItemId(int id)
{ {
return id.Place() switch int place = id.Place();
return place switch
{ {
8 => idAvatarMap![id], 8 => idAvatarMap![id],
5 => idWeaponMap![id], 5 => idWeaponMap![id],
_ => throw Must.NeverHappen(), _ => throw Must.NeverHappen($"Id places: {place}"),
}; };
} }

View File

@@ -143,6 +143,11 @@ internal class GameService : IGameService, IDisposable
string gamePath = GetGamePathSkipLocator(); string gamePath = GetGamePathSkipLocator();
string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFile); string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFile);
if (!File.Exists(configPath))
{
return new(null, null, configPath);
}
using (FileStream stream = File.OpenRead(configPath)) using (FileStream stream = File.OpenRead(configPath))
{ {
List<IniElement> elements = IniSerializer.Deserialize(stream).ToList(); List<IniElement> elements = IniSerializer.Deserialize(stream).ToList();

View File

@@ -18,14 +18,21 @@ public struct MultiChannel
/// </summary> /// </summary>
public string SubChannel; public string SubChannel;
/// <summary>
/// 配置文件路径 当不为 null 时则存在文件读写问题
/// </summary>
public string? ConfigFilePath;
/// <summary> /// <summary>
/// 构造一个新的多通道 /// 构造一个新的多通道
/// </summary> /// </summary>
/// <param name="channel">通道</param> /// <param name="channel">通道</param>
/// <param name="subChannel">子通道</param> /// <param name="subChannel">子通道</param>
public MultiChannel(string? channel, string? subChannel) /// <param name="configFilePath">配置文件路径</param>
public MultiChannel(string? channel, string? subChannel, string? configFilePath = null)
{ {
Channel = channel ?? string.Empty; Channel = channel ?? string.Empty;
SubChannel = subChannel ?? string.Empty; SubChannel = subChannel ?? string.Empty;
ConfigFilePath = configFilePath;
} }
} }

View File

@@ -6,6 +6,7 @@ using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Win32; using Snap.Hutao.Win32;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Windows.Win32.Foundation;
using Windows.Win32.System.Diagnostics.ToolHelp; using Windows.Win32.System.Diagnostics.ToolHelp;
using static Windows.Win32.PInvoke; using static Windows.Win32.PInvoke;
@@ -75,21 +76,24 @@ internal class GameFpsUnlocker : IGameFpsUnlocker
private static unsafe MODULEENTRY32 FindModule(int processId, string moduleName) private static unsafe MODULEENTRY32 FindModule(int processId, string moduleName)
{ {
using (SafeFileHandle snapshot = CreateToolhelp32Snapshot_SafeHandle(CREATE_TOOLHELP_SNAPSHOT_FLAGS.TH32CS_SNAPMODULE, (uint)processId)) HANDLE snapshot = CreateToolhelp32Snapshot(CREATE_TOOLHELP_SNAPSHOT_FLAGS.TH32CS_SNAPMODULE, (uint)processId);
using (snapshot.AutoClose())
{ {
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError()); Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
MODULEENTRY32 entry = StructMarshal.MODULEENTRY32(); MODULEENTRY32 entry = StructMarshal.MODULEENTRY32();
bool found = false; bool found = false;
// First module must be exe. Ignoring it. bool loop = Module32First(snapshot, &entry);
for (Module32First(snapshot, ref entry); Module32Next(snapshot, ref entry);) while (loop)
{ {
if (entry.th32ProcessID == processId && entry.szModule.AsString() == moduleName) if (entry.th32ProcessID == processId && entry.szModule.AsString() == moduleName)
{ {
found = true; found = true;
break; break;
} }
loop = Module32Next(snapshot, &entry);
} }
return found ? entry : default; return found ? entry : default;

View File

@@ -181,7 +181,7 @@ internal class NavigationService : INavigationService
/// <inheritdoc/> /// <inheritdoc/>
public void GoBack() public void GoBack()
{ {
Program.DispatcherQueue!.TryEnqueue(() => ThreadHelper.InvokeOnMainThread(() =>
{ {
bool canGoBack = Frame?.CanGoBack ?? false; bool canGoBack = Frame?.CanGoBack ?? false;

View File

@@ -124,7 +124,14 @@ internal class UserService : IUserService
} }
userCollection = users.ToObservableCollection(); userCollection = users.ToObservableCollection();
Current = users.SingleOrDefault(user => user.IsSelected); try
{
Current = users.SingleOrDefault(user => user.IsSelected);
}
catch (InvalidOperationException ex)
{
throw new Core.ExceptionService.UserdataCorruptedException("无法设置当前用户", ex);
}
} }
return userCollection; return userCollection;
@@ -157,8 +164,16 @@ internal class UserService : IUserService
{ {
if (userCollection != null) if (userCollection != null)
{ {
// TODO: optimize match speed. try
return userCollection.SelectMany(u => u.UserGameRoles).SingleOrDefault(r => r.GameUid == uid); {
// TODO: optimize match speed.
return userCollection.SelectMany(u => u.UserGameRoles).SingleOrDefault(r => r.GameUid == uid);
}
catch (InvalidOperationException)
{
// Sequence contains more than one matching element
// TODO: return a specialize UserGameRole to indicate error
}
} }
return null; return null;

View File

@@ -37,9 +37,12 @@
<ItemGroup> <ItemGroup>
<None Remove="Control\Panel\PanelSelector.xaml" /> <None Remove="Control\Panel\PanelSelector.xaml" />
<None Remove="Control\Theme\FontStyle.xaml" />
<None Remove="LaunchGameWindow.xaml" /> <None Remove="LaunchGameWindow.xaml" />
<None Remove="NativeMethods.json" /> <None Remove="NativeMethods.json" />
<None Remove="NativeMethods.txt" /> <None Remove="NativeMethods.txt" />
<None Remove="Resource\Font\CascadiaMono.ttf" />
<None Remove="Resource\Font\MiSans-Regular.ttf" />
<None Remove="Resource\Icon\UI_AchievementIcon_3_3.png" /> <None Remove="Resource\Icon\UI_AchievementIcon_3_3.png" />
<None Remove="Resource\Icon\UI_BagTabIcon_Avatar.png" /> <None Remove="Resource\Icon\UI_BagTabIcon_Avatar.png" />
<None Remove="Resource\Icon\UI_BagTabIcon_Weapon.png" /> <None Remove="Resource\Icon\UI_BagTabIcon_Weapon.png" />
@@ -122,7 +125,8 @@
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" /> <Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" /> <Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" /> <Content Include="Assets\Wide310x150Logo.scale-200.png" />
<Content Include="Resource\Font\Segoe Fluent Icons.ttf" /> <Content Include="Resource\Font\CascadiaMono.ttf" />
<Content Include="Resource\Font\MiSans-Regular.ttf" />
<Content Include="Resource\Icon\UI_AchievementIcon_3_3.png" /> <Content Include="Resource\Icon\UI_AchievementIcon_3_3.png" />
<Content Include="Resource\Icon\UI_BagTabIcon_Avatar.png" /> <Content Include="Resource\Icon\UI_BagTabIcon_Avatar.png" />
<Content Include="Resource\Icon\UI_BagTabIcon_Weapon.png" /> <Content Include="Resource\Icon\UI_BagTabIcon_Weapon.png" />
@@ -154,8 +158,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" /> <PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.1"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -171,14 +175,14 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25267-preview" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25276-preview" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221209.1" /> <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221209.1" />
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435"> <PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="TaskScheduler" Version="2.10.1" /> <PackageReference Include="TaskScheduler" Version="2.10.1" />
<PackageReference Include="WinUICommunity.SettingsUI" Version="3.0.0" /> <PackageReference Include="WinUICommunity.SettingsUI" Version="3.0.1" />
<Manifest Include="$(ApplicationManifest)" /> <Manifest Include="$(ApplicationManifest)" />
</ItemGroup> </ItemGroup>
@@ -415,4 +419,9 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Page Update="Control\Style\FontStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project> </Project>

View File

@@ -64,25 +64,18 @@
<DataTemplate x:Key="OrangeGridTemplate" x:DataType="shmbg:SummaryItem"> <DataTemplate x:Key="OrangeGridTemplate" x:DataType="shmbg:SummaryItem">
<Grid Width="40" Margin="0,4,4,0"> <Grid Width="40" Margin="0,4,4,0">
<Border <Border Style="{StaticResource BorderCardStyle}" ToolTipService.ToolTip="{Binding TimeFormatted}">
Background="{StaticResource CardBackgroundFillColorDefault}"
BorderBrush="{StaticResource CardStrokeColorDefault}"
BorderThickness="1"
CornerRadius="{StaticResource CompatCornerRadius}"
ToolTipService.ToolTip="{Binding TimeFormatted}">
<StackPanel> <StackPanel>
<shvc:ItemIcon <shvc:ItemIcon
Width="40" Width="40"
Height="40" Height="40"
Icon="{Binding Icon}" Icon="{Binding Icon}"
Quality="QUALITY_ORANGE"/> Quality="QUALITY_ORANGE"/>
<!--<shci:CachedImage
Source="{Binding Icon}"
Height="40" Width="40"/>-->
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalTextAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding LastPull}" Text="{Binding LastPull}"
TextTrimming="None"
TextWrapping="NoWrap"> TextWrapping="NoWrap">
<TextBlock.Foreground> <TextBlock.Foreground>
<SolidColorBrush Color="{Binding Color}"/> <SolidColorBrush Color="{Binding Color}"/>
@@ -94,7 +87,7 @@
</DataTemplate> </DataTemplate>
</UserControl.Resources> </UserControl.Resources>
<Border Background="{StaticResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{StaticResource CompatCornerRadius}"> <Border Style="{StaticResource BorderCardStyle}">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
@@ -108,19 +101,23 @@
HorizontalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"
Background="Transparent" Background="Transparent"
BorderBrush="{x:Null}" BorderBrush="{x:Null}"
BorderThickness="0"
CornerRadius="4,4,0,0"
IsExpanded="True"> IsExpanded="True">
<Expander.Resources>
<Thickness x:Key="ExpanderHeaderBorderThickness">0,0,0,1</Thickness>
</Expander.Resources>
<Expander.Header> <Expander.Header>
<Grid Grid.Row="0"> <Grid>
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}" Style="{StaticResource SubtitleTextBlockStyle}"
Text="{Binding Name}"/> Text="{Binding Name}"/>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal"> <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock <TextBlock
Margin="0,4,12,2" Margin="0,0,6,0"
VerticalAlignment="Bottom" VerticalAlignment="Center"
FontFamily="Consolas" FontSize="20"
FontSize="24"
Text="{Binding TotalCount}" Text="{Binding TotalCount}"
Visibility="{Binding ElementName=DetailExpander, Path=IsExpanded, Converter={StaticResource BoolToVisibilityRevertConverter}}"/> Visibility="{Binding ElementName=DetailExpander, Path=IsExpanded, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
<shcp:PanelSelector <shcp:PanelSelector
@@ -135,7 +132,7 @@
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock <TextBlock
Margin="0,4,0,4" Margin="0,4,0,4"
FontFamily="Consolas" FontFamily="{StaticResource CascadiaMonoAndMiSans}"
FontSize="48" FontSize="48"
Text="{Binding TotalCount}"/> Text="{Binding TotalCount}"/>
<TextBlock <TextBlock
@@ -149,12 +146,7 @@
<ColumnDefinition/> <ColumnDefinition/>
<ColumnDefinition/> <ColumnDefinition/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Border <Border Grid.Column="0" Style="{StaticResource BorderCardStyle}">
Grid.Column="0"
Background="{StaticResource CardBackgroundFillColorDefault}"
BorderBrush="{StaticResource CardStrokeColorDefault}"
BorderThickness="1"
CornerRadius="{StaticResource CompatCornerRadius}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/> <ColumnDefinition Width="auto"/>
@@ -165,14 +157,13 @@
Width="40" Width="40"
Height="40" Height="40"
Margin="4" Margin="4"
Background="{StaticResource CardBackgroundFillColorDefaultBrush}" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
Foreground="{StaticResource OrangeBrush}" Foreground="{StaticResource OrangeBrush}"
IsIndeterminate="False" IsIndeterminate="False"
Maximum="{Binding GuarenteeOrangeThreshold}" Maximum="{Binding GuarenteeOrangeThreshold}"
Value="{Binding LastOrangePull}"/> Value="{Binding LastOrangePull}"/>
<TextBlock <TextBlock
Grid.Column="0" Grid.Column="0"
Margin="0,0,0,2"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}" Foreground="{StaticResource OrangeBrush}"
@@ -180,7 +171,6 @@
Text="{Binding LastOrangePull}"/> Text="{Binding LastOrangePull}"/>
<TextBlock <TextBlock
Grid.Column="1" Grid.Column="1"
Margin="0,0,0,2"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}" Foreground="{StaticResource OrangeBrush}"
@@ -188,12 +178,7 @@
Text="五星"/> Text="五星"/>
</Grid> </Grid>
</Border> </Border>
<Border <Border Grid.Column="1" Style="{StaticResource BorderCardStyle}">
Grid.Column="1"
Background="{StaticResource CardBackgroundFillColorDefault}"
BorderBrush="{StaticResource CardStrokeColorDefault}"
BorderThickness="1"
CornerRadius="{StaticResource CompatCornerRadius}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/> <ColumnDefinition Width="auto"/>
@@ -204,14 +189,13 @@
Width="40" Width="40"
Height="40" Height="40"
Margin="4" Margin="4"
Background="{StaticResource CardBackgroundFillColorDefaultBrush}" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
Foreground="{StaticResource PurpleBrush}" Foreground="{StaticResource PurpleBrush}"
IsIndeterminate="False" IsIndeterminate="False"
Maximum="{Binding GuarenteePurpleThreshold}" Maximum="{Binding GuarenteePurpleThreshold}"
Value="{Binding LastPurplePull}"/> Value="{Binding LastPurplePull}"/>
<TextBlock <TextBlock
Grid.Column="0" Grid.Column="0"
Margin="0,0,0,2"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}" Foreground="{StaticResource PurpleBrush}"
@@ -219,7 +203,6 @@
Text="{Binding LastPurplePull}"/> Text="{Binding LastPurplePull}"/>
<TextBlock <TextBlock
Grid.Column="1" Grid.Column="1"
Margin="0,0,0,2"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}" Foreground="{StaticResource PurpleBrush}"
@@ -232,20 +215,17 @@
<StackPanel Margin="0,8,0,0" Orientation="Horizontal"> <StackPanel Margin="0,8,0,0" Orientation="Horizontal">
<TextBlock <TextBlock
HorizontalAlignment="Left" HorizontalAlignment="Left"
FontFamily="Consolas"
Opacity="0.6" Opacity="0.6"
Style="{StaticResource CaptionTextBlockStyle}" Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding FromFormatted}"/> Text="{Binding FromFormatted}"/>
<TextBlock <TextBlock
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
FontFamily="Consolas"
Opacity="0.6" Opacity="0.6"
Style="{StaticResource CaptionTextBlockStyle}" Style="{StaticResource CaptionTextBlockStyle}"
Text="-"/> Text="-"/>
<TextBlock <TextBlock
HorizontalAlignment="Left" HorizontalAlignment="Left"
FontFamily="Consolas"
Opacity="0.6" Opacity="0.6"
Style="{StaticResource CaptionTextBlockStyle}" Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding ToFormatted}"/> Text="{Binding ToFormatted}"/>
@@ -260,7 +240,7 @@
Text="五星"/> Text="五星"/>
<TextBlock <TextBlock
HorizontalAlignment="Right" HorizontalAlignment="Right"
FontFamily="Consolas" FontFamily="{StaticResource CascadiaMonoAndMiSans}"
Foreground="{StaticResource OrangeBrush}" Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource BodyTextBlockStyle}" Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding TotalOrangeFormatted}"/> Text="{Binding TotalOrangeFormatted}"/>
@@ -272,7 +252,7 @@
Text="四星"/> Text="四星"/>
<TextBlock <TextBlock
HorizontalAlignment="Right" HorizontalAlignment="Right"
FontFamily="Consolas" FontFamily="{StaticResource CascadiaMonoAndMiSans}"
Foreground="{StaticResource PurpleBrush}" Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource BodyTextBlockStyle}" Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding TotalPurpleFormatted}"/> Text="{Binding TotalPurpleFormatted}"/>
@@ -284,7 +264,7 @@
Text="三星"/> Text="三星"/>
<TextBlock <TextBlock
HorizontalAlignment="Right" HorizontalAlignment="Right"
FontFamily="Consolas" FontFamily="{StaticResource CascadiaMonoAndMiSans}"
Foreground="{StaticResource BlueBrush}" Foreground="{StaticResource BlueBrush}"
Style="{StaticResource BodyTextBlockStyle}" Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding TotalBlueFormatted}"/> Text="{Binding TotalBlueFormatted}"/>
@@ -294,7 +274,6 @@
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="五星平均抽数"/> <TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="五星平均抽数"/>
<TextBlock <TextBlock
HorizontalAlignment="Right" HorizontalAlignment="Right"
FontFamily="Consolas,MicroSoft YaHei UI"
Style="{StaticResource BodyTextBlockStyle}" Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding AverageOrangePullFormatted}"/> Text="{Binding AverageOrangePullFormatted}"/>
</Grid> </Grid>
@@ -302,19 +281,16 @@
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="UP 平均抽数"/> <TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="UP 平均抽数"/>
<TextBlock <TextBlock
HorizontalAlignment="Right" HorizontalAlignment="Right"
FontFamily="Consolas,MicroSoft YaHei UI"
Style="{StaticResource BodyTextBlockStyle}" Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding AverageUpOrangePullFormatted}"/> Text="{Binding AverageUpOrangePullFormatted}"/>
</Grid> </Grid>
<Grid Margin="0,2,0,0"> <Grid Margin="0,2,0,0">
<TextBlock <TextBlock
HorizontalAlignment="Left" HorizontalAlignment="Left"
FontFamily="Consolas,MicroSoft YaHei UI"
Style="{StaticResource BodyTextBlockStyle}" Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding MaxOrangePullFormatted}"/> Text="{Binding MaxOrangePullFormatted}"/>
<TextBlock <TextBlock
HorizontalAlignment="Right" HorizontalAlignment="Right"
FontFamily="Consolas,MicroSoft YaHei UI"
Style="{StaticResource BodyTextBlockStyle}" Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding MinOrangePullFormatted}"/> Text="{Binding MinOrangePullFormatted}"/>
</Grid> </Grid>

View File

@@ -4,17 +4,18 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="米游社养成计算"
Closed="OnContentDialogClosed" Closed="OnContentDialogClosed"
DefaultButton="Primary"
PrimaryButtonText="完成"
Style="{StaticResource DefaultContentDialogStyle}" Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d"> mc:Ignorable="d">
<ContentControl.Resources>
<Thickness x:Key="ContentDialogPadding">0</Thickness>
<CornerRadius x:Key="OverlayCornerRadius">0</CornerRadius>
</ContentControl.Resources>
<Grid Loaded="OnGridLoaded"> <Grid Loaded="OnGridLoaded">
<WebView2 <WebView2
Name="WebView" Name="WebView"
Width="380" Width="380"
Height="500"/> Height="500"
AllowFocusOnInteraction="False"/>
</Grid> </Grid>
</ContentDialog> </ContentDialog>

View File

@@ -8,15 +8,17 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Closed="OnContentDialogClosed" Closed="OnContentDialogClosed"
DefaultButton="Primary"
PrimaryButtonText="完成"
Style="{StaticResource DefaultContentDialogStyle}" Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d"> mc:Ignorable="d">
<ContentControl.Resources>
<Thickness x:Key="ContentDialogPadding">0</Thickness>
<CornerRadius x:Key="OverlayCornerRadius">0</CornerRadius>
</ContentControl.Resources>
<Grid Loaded="OnGridLoaded"> <Grid Loaded="OnGridLoaded">
<WebView2 <WebView2
Name="WebView" Name="WebView"
Width="360" Width="360"
Height="580"/> Height="580"
AllowFocusOnInteraction="True"/>
</Grid> </Grid>
</ContentDialog> </ContentDialog>

View File

@@ -4,17 +4,19 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="米游社实时便笺"
Closed="OnContentDialogClosed" Closed="OnContentDialogClosed"
DefaultButton="Primary"
PrimaryButtonText="完成"
Style="{StaticResource DefaultContentDialogStyle}" Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d"> mc:Ignorable="d">
<ContentControl.Resources>
<Thickness x:Key="ContentDialogPadding">0</Thickness>
<CornerRadius x:Key="OverlayCornerRadius">0</CornerRadius>
</ContentControl.Resources>
<Grid Loaded="OnGridLoaded"> <Grid Loaded="OnGridLoaded">
<WebView2 <WebView2
Name="WebView" Name="WebView"
Width="380" Width="380"
Height="448"/> Height="448"
AllowFocusOnInteraction="False"
DefaultBackgroundColor="Transparent"/>
</Grid> </Grid>
</ContentDialog> </ContentDialog>

View File

@@ -1,4 +1,4 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -11,20 +11,19 @@ using Snap.Hutao.Web.Bridge;
namespace Snap.Hutao.View.Dialog; namespace Snap.Hutao.View.Dialog;
/// <summary> /// <summary>
/// ʵʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><EFBFBD><EFBFBD> /// 实时便笺验证对话框
/// </summary> /// </summary>
public sealed partial class DailyNoteVerificationDialog : ContentDialog public sealed partial class DailyNoteVerificationDialog : ContentDialog
{ {
private readonly IServiceScope scope; private readonly IServiceScope scope;
private readonly UserAndUid userAndUid; private readonly UserAndUid userAndUid;
[SuppressMessage("", "IDE0052")]
private DailyNoteJsInterface? dailyNoteJsInterface; private DailyNoteJsInterface? dailyNoteJsInterface;
/// <summary> /// <summary>
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD>µ<EFBFBD>ʵʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><EFBFBD><EFBFBD> /// 构造一个新的实时便笺验证对话框
/// </summary> /// </summary>
/// <param name="userAndUid"><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ</param> /// <param name="userAndUid">用户与角色</param>
public DailyNoteVerificationDialog(UserAndUid userAndUid) public DailyNoteVerificationDialog(UserAndUid userAndUid)
{ {
InitializeComponent(); InitializeComponent();
@@ -46,14 +45,21 @@ public sealed partial class DailyNoteVerificationDialog : ContentDialog
Model.Entity.User user = userAndUid.User; Model.Entity.User user = userAndUid.User;
coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent(); coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent();
dailyNoteJsInterface = new(coreWebView2, scope.ServiceProvider); dailyNoteJsInterface = new(coreWebView2, scope.ServiceProvider);
dailyNoteJsInterface.ClosePageRequested += OnClosePageRequested;
string query = $"?role_id={userAndUid.Uid.Value}&server={userAndUid.Uid.Region}"; string query = $"?role_id={userAndUid.Uid.Value}&server={userAndUid.Uid.Region}";
coreWebView2.Navigate($"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/{query}"); coreWebView2.Navigate($"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/{query}");
} }
private void OnClosePageRequested()
{
ThreadHelper.InvokeOnMainThread(Hide);
}
private void OnContentDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args) private void OnContentDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
{ {
dailyNoteJsInterface.ClosePageRequested -= OnClosePageRequested;
dailyNoteJsInterface = null; dailyNoteJsInterface = null;
scope.Dispose(); scope.Dispose();
} }
} }

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Web.WebView2.Core;
using System.Diagnostics;
namespace Snap.Hutao.View.Extension;
/// <summary>
/// <see cref="CoreWebView2Environment"/> 扩展
/// </summary>
internal static class CoreWebView2EnvironmentExtension
{
/// <summary>
/// 退出
/// </summary>
/// <param name="environment">环境</param>
public static void Exit(this CoreWebView2Environment environment)
{
IReadOnlyList<CoreWebView2ProcessInfo> processInfos = environment.GetProcessInfos();
foreach (CoreWebView2ProcessInfo processInfo in processInfos)
{
Process p = Process.GetProcessById(processInfo.ProcessId);
if (p.ProcessName == "msedgewebview2.exe")
{
p.Kill();
}
}
}
}

View File

@@ -160,6 +160,9 @@
Margin="16,0,0,16" Margin="16,0,0,16"
ItemsPanel="{StaticResource ItemsStackPanelTemplate}" ItemsPanel="{StaticResource ItemsStackPanelTemplate}"
ItemsSource="{Binding Achievements}"> ItemsSource="{Binding Achievements}">
<ItemsControl.ItemContainerTransitions>
<ContentThemeTransition/>
</ItemsControl.ItemContainerTransitions>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid <Grid

View File

@@ -6,6 +6,8 @@ using Microsoft.UI.Xaml.Navigation;
using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Core; using Snap.Hutao.Core;
using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Extension;
using System.Diagnostics;
using Windows.System; using Windows.System;
namespace Snap.Hutao.View.Page; namespace Snap.Hutao.View.Page;
@@ -34,10 +36,12 @@ public sealed partial class AnnouncementContentPage : Microsoft.UI.Xaml.Controls
private const string DarkAccentColor2 = "background-color: rgb(254, 245, 231);"; private const string DarkAccentColor2 = "background-color: rgb(254, 245, 231);";
// support click open browser. // support click open browser.
private const string MihoyoSDKDefinition = private const string MihoyoSDKDefinition = """
@"window.miHoYoGameJSSDK = { window.miHoYoGameJSSDK = {
openInBrowser: function(url){ window.chrome.webview.postMessage(url); }, openInBrowser: function(url){ window.chrome.webview.postMessage(url); },
openInWebview: function(url){ location.href = url }}"; openInWebview: function(url){ location.href = url }
}
""";
private string? targetContent; private string? targetContent;
@@ -61,6 +65,13 @@ openInWebview: function(url){ location.href = url }}";
} }
} }
/// <inheritdoc/>
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
//WebView.CoreWebView2.Environment.Exit();
}
private static string? ReplaceForeground(string? rawContent, ElementTheme theme) private static string? ReplaceForeground(string? rawContent, ElementTheme theme)
{ {
if (string.IsNullOrWhiteSpace(rawContent)) if (string.IsNullOrWhiteSpace(rawContent))

View File

@@ -149,7 +149,7 @@
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<Pivot> <Pivot Style="{StaticResource DefaultPivotStyle}">
<PivotItem <PivotItem
Content="{Binding Announcement.List[0]}" Content="{Binding Announcement.List[0]}"
ContentTemplate="{StaticResource AnnouncementTemplate}" ContentTemplate="{StaticResource AnnouncementTemplate}"

View File

@@ -203,8 +203,12 @@
Margin="4,0,0,2" Margin="4,0,0,2"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="#FFFFFFFF" Foreground="#FFFFFFFF"
HorizontalTextAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding SelectedAvatar.FetterLevel}"/> Text="{Binding SelectedAvatar.FetterLevel}"
TextAlignment="Center"
TextTrimming="None"
TextWrapping="NoWrap"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@@ -481,7 +481,8 @@
<wsc:Setting <wsc:Setting
Description="使用当前账号的Cookie信息刷新祈愿记录" Description="使用当前账号的Cookie信息刷新祈愿记录"
Header="Stoken 刷新" Header="Stoken 刷新"
Icon="{shcm:FontIcon Glyph=&#xE192;}"> Icon="{shcm:FontIcon Glyph=&#xE192;}"
Style="{StaticResource DefaultSettingStyle}">
<Button Command="{Binding RefreshByStokenCommand}" Content="获取"/> <Button Command="{Binding RefreshByStokenCommand}" Content="获取"/>
</wsc:Setting> </wsc:Setting>
<wsc:Setting <wsc:Setting

View File

@@ -468,7 +468,16 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive) private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive)
{ {
List<Model.Metadata.Achievement.Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false); List<Model.Metadata.Achievement.Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List<Model.Binding.Achievement.Achievement> combined = achievementService.GetAchievements(archive, rawAchievements); List<Model.Binding.Achievement.Achievement> combined;
try
{
combined = achievementService.GetAchievements(archive, rawAchievements);
}
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex);
return;
}
// Assemble achievements on the UI thread. // Assemble achievements on the UI thread.
await ThreadHelper.SwitchToMainThreadAsync(); await ThreadHelper.SwitchToMainThreadAsync();

View File

@@ -88,9 +88,15 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
{ {
if (value != null) if (value != null)
{ {
refreshSecondsEntry!.SetInt32(value.Value); if (!ScheduleTaskHelper.RegisterForDailyNoteRefresh(value.Value))
appDbContext.Settings.UpdateAndSave(refreshSecondsEntry!); {
ScheduleTaskHelper.RegisterForDailyNoteRefresh(value.Value); Ioc.Default.GetRequiredService<IInfoBarService>().Warning("注册计划任务失败,请使用管理员模式重试");
}
else
{
refreshSecondsEntry!.SetInt32(value.Value);
appDbContext.Settings.UpdateAndSave(refreshSecondsEntry!);
}
} }
} }
} }
@@ -170,24 +176,38 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
private async Task OpenUIAsync() private async Task OpenUIAsync()
{ {
UserAndUids = await userService.GetRoleCollectionAsync().ConfigureAwait(true); UserAndUids = await userService.GetRoleCollectionAsync().ConfigureAwait(false);
try
{
ThrowIfViewDisposed();
using (await DisposeLock.EnterAsync().ConfigureAwait(false))
{
ThrowIfViewDisposed();
await ThreadHelper.SwitchToMainThreadAsync();
refreshSecondsEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteRefreshSeconds, "480"); refreshSecondsEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteRefreshSeconds, "480");
selectedRefreshTime = refreshTimes.Single(t => t.Value == refreshSecondsEntry.GetInt32()); selectedRefreshTime = refreshTimes.Single(t => t.Value == refreshSecondsEntry.GetInt32());
ScheduleTaskHelper.RegisterForDailyNoteRefresh(480); OnPropertyChanged(nameof(SelectedRefreshTime));
OnPropertyChanged(nameof(SelectedRefreshTime)); ScheduleTaskHelper.RegisterForDailyNoteRefresh(480);
reminderNotifyEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString); reminderNotifyEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString);
isReminderNotification = reminderNotifyEntry.GetBoolean(); isReminderNotification = reminderNotifyEntry.GetBoolean();
OnPropertyChanged(nameof(IsReminderNotification)); OnPropertyChanged(nameof(IsReminderNotification));
silentModeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteSilentWhenPlayingGame, SettingEntryHelper.FalseString); silentModeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteSilentWhenPlayingGame, SettingEntryHelper.FalseString);
isSilentWhenPlayingGame = silentModeEntry.GetBoolean(); isSilentWhenPlayingGame = silentModeEntry.GetBoolean();
OnPropertyChanged(nameof(IsSilentWhenPlayingGame)); OnPropertyChanged(nameof(IsSilentWhenPlayingGame));
ObservableCollection<DailyNoteEntry> temp = await dailyNoteService.GetDailyNoteEntriesAsync().ConfigureAwait(false); await ThreadHelper.SwitchToBackgroundAsync();
await ThreadHelper.SwitchToMainThreadAsync(); }
DailyNoteEntries = temp;
ObservableCollection<DailyNoteEntry> temp = await dailyNoteService.GetDailyNoteEntriesAsync().ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
DailyNoteEntries = temp;
}
catch (OperationCanceledException)
{
}
} }
private async Task TrackRoleAsync(UserAndUid? role) private async Task TrackRoleAsync(UserAndUid? role)

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.Data.Sqlite;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension; using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO;
@@ -199,7 +200,26 @@ internal class GachaLogViewModel : Abstraction.ViewModel
GachaLogRefreshProgressDialog dialog = new(); GachaLogRefreshProgressDialog dialog = new();
IAsyncDisposable dialogHider = await dialog.BlockAsync().ConfigureAwait(false); IAsyncDisposable dialogHider = await dialog.BlockAsync().ConfigureAwait(false);
Progress<FetchState> progress = new(dialog.OnReport); Progress<FetchState> progress = new(dialog.OnReport);
bool authkeyValid = await gachaLogService.RefreshGachaLogAsync(query, strategy, progress, default).ConfigureAwait(false); bool authkeyValid;
using (await DisposeLock.EnterAsync().ConfigureAwait(false))
{
try
{
authkeyValid = await gachaLogService.RefreshGachaLogAsync(query, strategy, progress, CancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// We set true here in order to hide the dialog.
authkeyValid = true;
infoBarService.Warning("祈愿记录刷新操作被异常取消");
}
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
authkeyValid = false;
infoBarService.Error(ex);
}
}
await ThreadHelper.SwitchToMainThreadAsync(); await ThreadHelper.SwitchToMainThreadAsync();
if (authkeyValid) if (authkeyValid)

View File

@@ -221,13 +221,21 @@ internal class LaunchGameViewModel : Abstraction.ViewModel
ThrowIfViewDisposed(); ThrowIfViewDisposed();
MultiChannel multi = gameService.GetMultiChannel(); MultiChannel multi = gameService.GetMultiChannel();
SelectedScheme = KnownSchemes.FirstOrDefault(s => s.Channel == multi.Channel && s.SubChannel == multi.SubChannel); if (string.IsNullOrEmpty(multi.ConfigFilePath))
{
SelectedScheme = KnownSchemes.FirstOrDefault(s => s.Channel == multi.Channel && s.SubChannel == multi.SubChannel);
}
else
{
Ioc.Default.GetRequiredService<IInfoBarService>().Warning("无法读取游戏配置文件");
}
GameAccounts = await gameService.GetGameAccountCollectionAsync().ConfigureAwait(true); GameAccounts = await gameService.GetGameAccountCollectionAsync().ConfigureAwait(true);
// Sync uid // Sync uid
if (memoryCache.TryGetValue(DesiredUid, out object? value) && value is string uid) if (memoryCache.TryGetValue(DesiredUid, out object? value) && value is string uid)
{ {
SelectedGameAccount = GameAccounts.SingleOrDefault(g => g.AttachUid == uid); SelectedGameAccount = GameAccounts.FirstOrDefault(g => g.AttachUid == uid);
} }
// Sync from Settings // Sync from Settings

View File

@@ -207,7 +207,16 @@ internal class SettingViewModel : Abstraction.ViewModel
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>(); IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
if (Directory.Exists(cacheFolder)) if (Directory.Exists(cacheFolder))
{ {
Directory.Delete(cacheFolder, true); try
{
Directory.Delete(cacheFolder, true);
}
catch (UnauthorizedAccessException)
{
infoBarService.Warning($"清除失败,文件目录权限不足,请使用管理员模式重试");
return;
}
infoBarService.Success("清除完成"); infoBarService.Success("清除完成");
} }
else else

View File

@@ -52,40 +52,25 @@ internal class WelcomeViewModel : ObservableObject
private async Task OpenUIAsync() private async Task OpenUIAsync()
{ {
List<DownloadSummary> downloadSummaries = new(); IEnumerable<DownloadSummary> downloadSummaries = GenerateStaticResourceDownloadTasks();
if (!LocalSetting.Get(SettingKeys.StaticResourceV1Contract, false)) DownloadSummaries = downloadSummaries.ToObservableCollection();
{
downloadSummaries.Add(new(serviceProvider, "基础图标", "Bg"));
downloadSummaries.Add(new(serviceProvider, "角色图标", "AvatarIcon"));
downloadSummaries.Add(new(serviceProvider, "角色立绘图标", "GachaAvatarIcon"));
downloadSummaries.Add(new(serviceProvider, "角色立绘图像", "GachaAvatarImg"));
downloadSummaries.Add(new(serviceProvider, "武器图标", "EquipIcon"));
downloadSummaries.Add(new(serviceProvider, "武器立绘图标", "GachaEquipIcon"));
downloadSummaries.Add(new(serviceProvider, "名片图像", "NameCardPic"));
downloadSummaries.Add(new(serviceProvider, "天赋图标", "Skill"));
downloadSummaries.Add(new(serviceProvider, "命之座图标", "Talent"));
}
if (!LocalSetting.Get(SettingKeys.StaticResourceV2Contract, false))
{
downloadSummaries.Add(new(serviceProvider, "成就图标", "AchievementIcon"));
downloadSummaries.Add(new(serviceProvider, "物品图标", "ItemIcon"));
downloadSummaries.Add(new(serviceProvider, "元素图标", "IconElement"));
downloadSummaries.Add(new(serviceProvider, "圣遗物图标", "RelicIcon"));
}
DownloadSummaries = new(downloadSummaries);
// Cancel all previous created jobs // Cancel all previous created jobs
serviceProvider.GetRequiredService<BitsManager>().CancelAllJobs(); serviceProvider.GetRequiredService<BitsManager>().CancelAllJobs();
await Task.WhenAll(downloadSummaries.Select(d => d.DownloadAndExtractAsync())).ConfigureAwait(true); await Task.WhenAll(downloadSummaries.Select(async d =>
{
await d.DownloadAndExtractAsync().ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
DownloadSummaries.Remove(d);
})).ConfigureAwait(true);
serviceProvider.GetRequiredService<IMessenger>().Send(new Message.WelcomeStateCompleteMessage()); serviceProvider.GetRequiredService<IMessenger>().Send(new Message.WelcomeStateCompleteMessage());
// Complete StaticResourceContracts // Complete StaticResourceContracts
LocalSetting.Set(SettingKeys.StaticResourceV1Contract, true); LocalSetting.Set(SettingKeys.StaticResourceV1Contract, true);
LocalSetting.Set(SettingKeys.StaticResourceV2Contract, true); LocalSetting.Set(SettingKeys.StaticResourceV2Contract, true);
LocalSetting.Set(SettingKeys.StaticResourceV3Contract, true);
try try
{ {
@@ -100,10 +85,45 @@ internal class WelcomeViewModel : ObservableObject
} }
} }
private IEnumerable<DownloadSummary> GenerateStaticResourceDownloadTasks()
{
Dictionary<string, DownloadSummary> downloadSummaries = new();
if (!LocalSetting.Get(SettingKeys.StaticResourceV1Contract, false))
{
downloadSummaries.TryAdd("Bg", new(serviceProvider, "基础图标", "Bg"));
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "角色图标", "AvatarIcon"));
downloadSummaries.TryAdd("GachaAvatarIcon", new(serviceProvider, "角色立绘图标", "GachaAvatarIcon"));
downloadSummaries.TryAdd("GachaAvatarImg", new(serviceProvider, "角色立绘图像", "GachaAvatarImg"));
downloadSummaries.TryAdd("EquipIcon", new(serviceProvider, "武器图标", "EquipIcon"));
downloadSummaries.TryAdd("GachaEquipIcon", new(serviceProvider, "武器立绘图标", "GachaEquipIcon"));
downloadSummaries.TryAdd("NameCardPic", new(serviceProvider, "名片图像", "NameCardPic"));
downloadSummaries.TryAdd("Skill", new(serviceProvider, "天赋图标", "Skill"));
downloadSummaries.TryAdd("Talent", new(serviceProvider, "命之座图标", "Talent"));
}
if (!LocalSetting.Get(SettingKeys.StaticResourceV2Contract, false))
{
downloadSummaries.TryAdd("AchievementIcon", new(serviceProvider, "成就图标", "AchievementIcon"));
downloadSummaries.TryAdd("ItemIcon", new(serviceProvider, "物品图标", "ItemIcon"));
downloadSummaries.TryAdd("IconElement", new(serviceProvider, "元素图标", "IconElement"));
downloadSummaries.TryAdd("RelicIcon", new(serviceProvider, "圣遗物图标", "RelicIcon"));
}
if (!LocalSetting.Get(SettingKeys.StaticResourceV3Contract, false))
{
downloadSummaries.TryAdd("Skill", new(serviceProvider, "天赋图标更新", "Skill"));
downloadSummaries.TryAdd("Talent", new(serviceProvider, "命之座图标更新", "Talent"));
}
return downloadSummaries.Select(x => x.Value);
}
/// <summary> /// <summary>
/// 下载信息 /// 下载信息
/// </summary> /// </summary>
public class DownloadSummary : ObservableObject [SuppressMessage("", "CA1067")]
public class DownloadSummary : ObservableObject, IEquatable<DownloadSummary>
{ {
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly BitsManager bitsManager; private readonly BitsManager bitsManager;
@@ -145,6 +165,12 @@ internal class WelcomeViewModel : ObservableObject
/// </summary> /// </summary>
public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); } public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); }
/// <inheritdoc/>
public bool Equals(DownloadSummary? other)
{
return fileName == other?.fileName;
}
/// <summary> /// <summary>
/// 异步下载并解压 /// 异步下载并解压
/// </summary> /// </summary>

View File

@@ -211,25 +211,25 @@ internal class WikiAvatarViewModel : Abstraction.ViewModel
continue; continue;
} }
if (ImmutableIntrinsics.AssociationTypes.Contains(value)) if (IntrinsicImmutables.AssociationTypes.Contains(value))
{ {
keep = keep || avatar.FetterInfo.Association.GetDescriptionOrNull() == value; keep = keep || avatar.FetterInfo.Association.GetDescriptionOrNull() == value;
continue; continue;
} }
if (ImmutableIntrinsics.WeaponTypes.Contains(value)) if (IntrinsicImmutables.WeaponTypes.Contains(value))
{ {
keep = keep || avatar.Weapon.GetDescriptionOrNull() == value; keep = keep || avatar.Weapon.GetDescriptionOrNull() == value;
continue; continue;
} }
if (ImmutableIntrinsics.ItemQualities.Contains(value)) if (IntrinsicImmutables.ItemQualities.Contains(value))
{ {
keep = keep || avatar.Quality.GetDescriptionOrNull() == value; keep = keep || avatar.Quality.GetDescriptionOrNull() == value;
continue; continue;
} }
if (ImmutableIntrinsics.BodyTypes.Contains(value)) if (IntrinsicImmutables.BodyTypes.Contains(value))
{ {
keep = keep || avatar.Body.GetDescriptionOrNull() == value; keep = keep || avatar.Body.GetDescriptionOrNull() == value;
continue; continue;

View File

@@ -203,19 +203,19 @@ internal class WikiWeaponViewModel : Abstraction.ViewModel
{ {
string value = segment.ToString(); string value = segment.ToString();
if (ImmutableIntrinsics.WeaponTypes.Contains(value)) if (IntrinsicImmutables.WeaponTypes.Contains(value))
{ {
keep = keep || weapon.WeaponType.GetDescriptionOrNull() == value; keep = keep || weapon.WeaponType.GetDescriptionOrNull() == value;
continue; continue;
} }
if (ImmutableIntrinsics.ItemQualities.Contains(value)) if (IntrinsicImmutables.ItemQualities.Contains(value))
{ {
keep = keep || weapon.Quality.GetDescriptionOrNull() == value; keep = keep || weapon.Quality.GetDescriptionOrNull() == value;
continue; continue;
} }
if (ImmutableIntrinsics.FightProperties.Contains(value)) if (IntrinsicImmutables.FightProperties.Contains(value))
{ {
keep = keep || weapon.Property.Properties.ElementAtOrDefault(1).GetDescriptionOrNull() == value; keep = keep || weapon.Property.Properties.ElementAtOrDefault(1).GetDescriptionOrNull() == value;
continue; continue;

View File

@@ -41,6 +41,7 @@ public class MiHoYoJSInterface
private readonly CoreWebView2 webView; private readonly CoreWebView2 webView;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly ILogger<MiHoYoJSInterface> logger; private readonly ILogger<MiHoYoJSInterface> logger;
private readonly SemaphoreSlim webMessageSemaphore = new(1);
/// <summary> /// <summary>
/// 构造一个新的调用桥 /// 构造一个新的调用桥
@@ -59,6 +60,8 @@ public class MiHoYoJSInterface
webView.NavigationStarting += OnNavigationStarting; webView.NavigationStarting += OnNavigationStarting;
} }
public event Action? ClosePageRequested;
/// <summary> /// <summary>
/// 获取ActionTicket /// 获取ActionTicket
/// </summary> /// </summary>
@@ -160,7 +163,7 @@ public class MiHoYoJSInterface
string q = param.Payload.GetQueryParam(); string q = param.Payload.GetQueryParam();
string check = Md5Convert.ToHexString($"salt={salt}&t={t}&r={r}&b={b}&q={q}").ToLowerInvariant(); string check = Md5Convert.ToHexString($"salt={salt}&t={t}&r={r}&b={b}&q={q}").ToLowerInvariant();
return new() { Data = new() { ["DS"] = $"{t},{r},{check}", }, }; return new() { Data = new() { ["DS"] = $"{t},{r},{check}" } };
static int GetRandom() static int GetRandom()
{ {
@@ -214,9 +217,12 @@ public class MiHoYoJSInterface
cookieToken = cookieTokenResponse.Data.CookieToken; cookieToken = cookieTokenResponse.Data.CookieToken;
} }
// Check null and create a new one to avoid System.NullReferenceException
user.CookieToken ??= new();
// sync ui and database // sync ui and database
user.CookieToken![Cookie.COOKIE_TOKEN] = cookieToken!; user.CookieToken[Cookie.COOKIE_TOKEN] = cookieToken!;
Ioc.Default.GetRequiredService<AppDbContext>().Users.UpdateAndSave(user.Entity); serviceProvider.GetRequiredService<AppDbContext>().Users.UpdateAndSave(user.Entity);
} }
else else
{ {
@@ -234,6 +240,7 @@ public class MiHoYoJSInterface
[JsMethod("closePage")] [JsMethod("closePage")]
public virtual IJsResult? ClosePage(JsParam param) public virtual IJsResult? ClosePage(JsParam param)
{ {
ClosePageRequested?.Invoke();
return null; return null;
} }
@@ -256,7 +263,7 @@ public class MiHoYoJSInterface
[JsMethod("getStatusBarHeight")] [JsMethod("getStatusBarHeight")]
public virtual JsResult<Dictionary<string, object>> GetStatusBarHeight(JsParam param) public virtual JsResult<Dictionary<string, object>> GetStatusBarHeight(JsParam param)
{ {
return new() { Data = new() { { "statusBarHeight", 0 } } }; return new() { Data = new() { ["statusBarHeight"] = 0 } };
} }
[JsMethod("pushPage")] [JsMethod("pushPage")]
@@ -352,11 +359,14 @@ public class MiHoYoJSInterface
JsParam param = JsonSerializer.Deserialize<JsParam>(message)!; JsParam param = JsonSerializer.Deserialize<JsParam>(message)!;
logger.LogInformation("[OnMessage]\nMethod : {method}\nPayload : {payload}\nCallback: {callback}", param.Method, param.Payload, param.Callback); logger.LogInformation("[OnMessage]\nMethod : {method}\nPayload : {payload}\nCallback: {callback}", param.Method, param.Payload, param.Callback);
IJsResult? result = await TryGetJsResultFromJsParamAsync(param).ConfigureAwait(false); using (await webMessageSemaphore.EnterAsync().ConfigureAwait(false))
if (result != null && param.Callback != null)
{ {
await ExecuteCallbackScriptAsync(param.Callback, result.ToString()).ConfigureAwait(false); IJsResult? result = await TryGetJsResultFromJsParamAsync(param).ConfigureAwait(false);
if (result != null && param.Callback != null)
{
await ExecuteCallbackScriptAsync(param.Callback, result.ToString()).ConfigureAwait(false);
}
} }
} }

View File

@@ -45,7 +45,7 @@ public partial class Cookie
{ {
SortedDictionary<string, string> cookieMap = new(); SortedDictionary<string, string> cookieMap = new();
cookieString = cookieString.Replace(" ", string.Empty); cookieString = cookieString.Replace(" ", string.Empty);
string[] values = cookieString.TrimEnd(';').Split(';'); string[] values = cookieString.Split(';', StringSplitOptions.RemoveEmptyEntries);
foreach (string[] parts in values.Select(c => c.Split('=', 2))) foreach (string[] parts in values.Select(c => c.Split('=', 2)))
{ {
string name = parts[0].Trim(); string name = parts[0].Trim();

View File

@@ -15,8 +15,8 @@ public class Game
public Package Latest { get; set; } = default!; public Package Latest { get; set; } = default!;
/// <summary> /// <summary>
/// 差异文件 /// 相对于当前版本的之前版本的差异文件(非预下载)
/// </summary> /// </summary>
[JsonPropertyName("diffs")] [JsonPropertyName("diffs")]
public IList<DiffPackage> Diffs { get; set; } = default!; public List<DiffPackage> Diffs { get; set; } = default!;
} }

View File

@@ -21,13 +21,13 @@ public class GameResource
public Plugin Plugin { get; set; } = default!; public Plugin Plugin { get; set; } = default!;
/// <summary> /// <summary>
/// 官网地址 /// 官网地址 https://ys.mihoyo.com/launcher
/// </summary> /// </summary>
[JsonPropertyName("web_url")] [JsonPropertyName("web_url")]
public Uri WebUrl { get; set; } = default!; public Uri WebUrl { get; set; } = default!;
/// <summary> /// <summary>
/// 强制更新文件 /// 强制更新文件 null
/// </summary> /// </summary>
[JsonPropertyName("force_update")] [JsonPropertyName("force_update")]
public object? ForceUpdate { get; set; } public object? ForceUpdate { get; set; }
@@ -36,17 +36,23 @@ public class GameResource
/// 预下载 /// 预下载
/// </summary> /// </summary>
[JsonPropertyName("pre_download_game")] [JsonPropertyName("pre_download_game")]
public object? PreDownloadGame { get; set; } public Game? PreDownloadGame { get; set; }
/// <summary> /// <summary>
/// 过期更新包 /// 过期更新包
/// </summary> /// </summary>
[JsonPropertyName("deprecated_packages")] [JsonPropertyName("deprecated_packages")]
public List<LocalFile> DeprecatedPackages { get; set; } = default!; public List<NameMd5> DeprecatedPackages { get; set; } = default!;
/// <summary>
/// 渠道服sdk
/// </summary>
[JsonPropertyName("sdk")]
public object? Sdk { get; set; }
/// <summary> /// <summary>
/// 过期的单个文件 /// 过期的单个文件
/// </summary> /// </summary>
[JsonPropertyName("deprecated_files")] [JsonPropertyName("deprecated_files")]
public List<LocalFile>? DeprecatedFiles { get; set; } public List<NameMd5>? DeprecatedFiles { get; set; }
} }

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
/// <summary> /// <summary>
/// 资源文件 /// 资源文件
/// </summary> /// </summary>
public class LocalFile public class NameMd5
{ {
/// <summary> /// <summary>
/// 文件名称 /// 文件名称

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
/// <summary> /// <summary>
/// 最新客户端 /// 最新客户端
/// </summary> /// </summary>
public class Package : DownloadFile public class Package : PathMd5
{ {
/// <summary> /// <summary>
/// 名称 空 /// 名称 空
@@ -27,14 +27,22 @@ public class Package : DownloadFile
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public long Size { get; set; } = default!; public long Size { get; set; } = default!;
/// <summary>
/// 主程序相对路径 YuanShen.exe
/// </summary>
public string Entry { get; set; } = default!;
/// <summary> /// <summary>
/// 语音包 /// 语音包
/// </summary> /// </summary>
[JsonPropertyName("voice_packs")] [JsonPropertyName("voice_packs")]
public List<VoicePackage> VoicePacks { get; set; } = default!; public List<VoicePackage> VoicePacks { get; set; } = default!;
// We don't want to support
// decompressed_path & segments
/// <summary> /// <summary>
/// 包大小 /// 包大小 bytes
/// </summary> /// </summary>
[JsonPropertyName("package_size")] [JsonPropertyName("package_size")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
/// <summary> /// <summary>
/// 下载的文件 /// 下载的文件
/// </summary> /// </summary>
public class DownloadFile public class PathMd5
{ {
/// <summary> /// <summary>
/// 下载地址 /// 下载地址

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
/// <summary> /// <summary>
/// 插件项 /// 插件项
/// </summary> /// </summary>
public class PluginItem : LocalFile public class PluginItem : NameMd5
{ {
/// <summary> /// <summary>
/// 版本 一般为空 /// 版本 一般为空

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
/// <summary> /// <summary>
/// 语音包 /// 语音包
/// </summary> /// </summary>
public class VoicePackage : DownloadFile public class VoicePackage : PathMd5
{ {
/// <summary> /// <summary>
/// 语音 /// 语音

View File

@@ -51,7 +51,7 @@ internal class HomaClient
public async Task<Response<bool>> CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default) public async Task<Response<bool>> CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default)
{ {
Response<bool>? resp = await httpClient Response<bool>? resp = await httpClient
.GetFromJsonAsync<Response<bool>>(HutaoEndpoints.RecordCheck(uid.Value), token) .TryCatchGetFromJsonAsync<Response<bool>>(HutaoEndpoints.RecordCheck(uid.Value), options, logger, token)
.ConfigureAwait(false); .ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp); return Response.Response.DefaultIfNull(resp);
@@ -67,7 +67,7 @@ internal class HomaClient
public async Task<Response<RankInfo>> GetRankAsync(PlayerUid uid, CancellationToken token = default) public async Task<Response<RankInfo>> GetRankAsync(PlayerUid uid, CancellationToken token = default)
{ {
Response<RankInfo>? resp = await httpClient Response<RankInfo>? resp = await httpClient
.GetFromJsonAsync<Response<RankInfo>>(HutaoEndpoints.RecordRank(uid.Value), token) .TryCatchGetFromJsonAsync<Response<RankInfo>>(HutaoEndpoints.RecordRank(uid.Value), options, logger, token)
.ConfigureAwait(false); .ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp); return Response.Response.DefaultIfNull(resp);
@@ -82,7 +82,7 @@ internal class HomaClient
public async Task<Response<Overview>> GetOverviewAsync(CancellationToken token = default) public async Task<Response<Overview>> GetOverviewAsync(CancellationToken token = default)
{ {
Response<Overview>? resp = await httpClient Response<Overview>? resp = await httpClient
.GetFromJsonAsync<Response<Overview>>(HutaoEndpoints.StatisticsOverview, token) .TryCatchGetFromJsonAsync<Response<Overview>>(HutaoEndpoints.StatisticsOverview, options, logger, token)
.ConfigureAwait(false); .ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp); return Response.Response.DefaultIfNull(resp);

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Windows.Graphics; using Windows.Graphics;
using Windows.Win32.Foundation;
using static Windows.Win32.PInvoke;
namespace Snap.Hutao.Win32; namespace Snap.Hutao.Win32;
@@ -40,4 +42,29 @@ internal static class StructExtension
{ {
return sizeInt32.Width * sizeInt32.Height; return sizeInt32.Width * sizeInt32.Height;
} }
/// <summary>
/// 使用完成后自动关闭句柄
/// </summary>
/// <param name="handle">句柄</param>
/// <returns>用于关闭句柄的对象</returns>
public static IDisposable AutoClose(this HANDLE handle)
{
return new HandleCloser(handle);
}
private readonly struct HandleCloser : IDisposable
{
private readonly HANDLE handle;
public HandleCloser(HANDLE handle)
{
this.handle = handle;
}
public void Dispose()
{
CloseHandle(handle);
}
}
} }