mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
replace font
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
# [Snap.Hutao](https://hut.ao)
|
||||
|
||||

|
||||
|
||||
> 唷,找本堂主有何贵干呀?
|
||||
|
||||

|
||||
|
||||
# 特别感谢
|
||||
|
||||
### 原神组织与个人
|
||||
|
||||
* [HolographicHat](https://github.com/HolographicHat)
|
||||
* [UIGF organization](https://uigf.org)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<muxc:XamlControlsResources/>
|
||||
<ResourceDictionary Source="ms-appx:///SettingsUI/Themes/Generic.xaml"/>
|
||||
<ResourceDictionary Source="Control/Theme/FontStyle.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
@@ -27,8 +28,6 @@
|
||||
<StaticResource x:Key="WindowCaptionBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
<!-- Page Transparent Background -->
|
||||
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
<!-- IconFont -->
|
||||
<FontFamily x:Key="SymbolThemeFontFamily">ms-appx:///Resource/Font/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
|
||||
<!-- InfoBar Resource -->
|
||||
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>
|
||||
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
|
||||
@@ -90,6 +89,7 @@
|
||||
<shvc:Int32ToVisibilityConverter x:Key="Int32ToVisibilityConverter"/>
|
||||
<shvc:Int32ToVisibilityRevertConverter x:Key="Int32ToVisibilityRevertConverter"/>
|
||||
<!-- Styles -->
|
||||
|
||||
<Style
|
||||
x:Key="LargeGridViewItemStyle"
|
||||
BasedOn="{StaticResource DefaultGridViewItemStyle}"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
|
||||
210
src/Snap.Hutao/Snap.Hutao/Control/Theme/FontStyle.xaml
Normal file
210
src/Snap.Hutao/Snap.Hutao/Control/Theme/FontStyle.xaml
Normal 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>
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
@@ -24,7 +25,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
{
|
||||
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),
|
||||
[1] = TimeSpan.FromSeconds(16),
|
||||
@@ -32,13 +33,13 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
[3] = TimeSpan.FromSeconds(4),
|
||||
[4] = TimeSpan.FromSeconds(16),
|
||||
[5] = TimeSpan.FromSeconds(64),
|
||||
}.ToImmutableDictionary();
|
||||
};
|
||||
|
||||
private readonly ILogger logger;
|
||||
|
||||
// violate di rule
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
||||
|
||||
private string? baseFolder;
|
||||
private string? cacheFolder;
|
||||
|
||||
@@ -100,12 +101,31 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
/// <inheritdoc/>
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -191,7 +211,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
|
||||
if (retryCount == 3)
|
||||
{
|
||||
uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao, }.Uri;
|
||||
uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao }.Uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
||||
/// </summary>
|
||||
public static class ServiceScopeExtension
|
||||
{
|
||||
private static IServiceScope? scopeReference;
|
||||
// Allow GC to Collect the IServiceScope
|
||||
private static readonly WeakReference<IServiceScope> ScopeReference = new(null!);
|
||||
|
||||
/// <summary>
|
||||
/// 追踪服务范围
|
||||
@@ -19,7 +20,7 @@ public static class ServiceScopeExtension
|
||||
public static void Track(this IServiceScope scope)
|
||||
{
|
||||
DisposeLast();
|
||||
scopeReference = scope;
|
||||
ScopeReference.SetTarget(scope);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -27,6 +28,9 @@ public static class ServiceScopeExtension
|
||||
/// </summary>
|
||||
public static void DisposeLast()
|
||||
{
|
||||
scopeReference?.Dispose();
|
||||
if (ScopeReference.TryGetTarget(out IServiceScope? scope))
|
||||
{
|
||||
scope.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32.TaskScheduler;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using SchedulerTask = Microsoft.Win32.TaskScheduler.Task;
|
||||
|
||||
@@ -23,6 +24,7 @@ internal static class ScheduleTaskHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 似乎可以不删除任务,直接注册已经包含了更新功能
|
||||
SchedulerTask? targetTask = TaskService.Instance.GetTask(DailyNoteRefreshTaskName);
|
||||
if (targetTask != null)
|
||||
{
|
||||
@@ -36,12 +38,9 @@ internal static class ScheduleTaskHelper
|
||||
TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task);
|
||||
return true;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (COMException)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_ = ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,4 +32,9 @@ internal static class SettingKeys
|
||||
/// 静态资源合约V2 成就图标与物品图标
|
||||
/// </summary>
|
||||
public const string StaticResourceV2Contract = "StaticResourceV2Contract";
|
||||
|
||||
/// <summary>
|
||||
/// 静态资源合约V3 刷新 Skill Talent
|
||||
/// </summary>
|
||||
public const string StaticResourceV3Contract = "StaticResourceV3Contract";
|
||||
}
|
||||
@@ -39,6 +39,13 @@ internal static class ThreadHelper
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void InvokeOnMainThread(Action action)
|
||||
{
|
||||
Program.DispatcherQueue!.Invoke(action);
|
||||
if (Program.DispatcherQueue!.HasThreadAccess)
|
||||
{
|
||||
action();
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.DispatcherQueue.Invoke(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Model.Intrinsic;
|
||||
/// <summary>
|
||||
/// 不可变的原生枚举
|
||||
/// </summary>
|
||||
public static class ImmutableIntrinsics
|
||||
public static class IntrinsicImmutables
|
||||
{
|
||||
/// <summary>
|
||||
/// 所属地区
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.3.9.0" />
|
||||
Version="1.3.10.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Font/CascadiaMono.ttf
Normal file
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Font/CascadiaMono.ttf
Normal file
Binary file not shown.
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Font/MiSans-Regular.ttf
Normal file
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Font/MiSans-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
@@ -114,7 +115,17 @@ internal class AchievementService : IAchievementService
|
||||
List<BindingAchievement> results = new();
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace Snap.Hutao.Service.AvatarInfo;
|
||||
[Injection(InjectAs.Scoped, typeof(IAvatarInfoService))]
|
||||
internal class AvatarInfoService : IAvatarInfoService
|
||||
{
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly ISummaryFactory summaryFactory;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly ILogger<AvatarInfoService> logger;
|
||||
@@ -42,7 +41,6 @@ internal class AvatarInfoService : IAvatarInfoService
|
||||
ISummaryFactory summaryFactory,
|
||||
ILogger<AvatarInfoService> logger)
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
this.metadataService = metadataService;
|
||||
this.summaryFactory = summaryFactory;
|
||||
this.logger = logger;
|
||||
|
||||
@@ -43,7 +43,7 @@ internal class SummaryAvatarFactory
|
||||
ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull());
|
||||
MetadataAvatar avatar = metadataContext.IdAvatarMap[avatarInfo.AvatarId];
|
||||
|
||||
return new()
|
||||
PropertyAvatar propertyAvatar = new()
|
||||
{
|
||||
Id = avatar.Id,
|
||||
Name = avatar.Name,
|
||||
@@ -52,8 +52,6 @@ internal class SummaryAvatarFactory
|
||||
NameCard = AvatarNameCardPicConverter.AvatarToUri(avatar),
|
||||
Quality = avatar.Quality,
|
||||
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,
|
||||
Weapon = reliquaryAndWeapon.Weapon,
|
||||
Reliquaries = reliquaryAndWeapon.Reliquaries,
|
||||
@@ -62,7 +60,11 @@ internal class SummaryAvatarFactory
|
||||
Properties = SummaryHelper.CreateAvatarProperties(avatarInfo.FightPropMap),
|
||||
Score = reliquaryAndWeapon.Reliquaries.Sum(r => r.Score).ToString("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)
|
||||
@@ -85,7 +87,7 @@ internal class SummaryAvatarFactory
|
||||
}
|
||||
}
|
||||
|
||||
return new(reliquaryList, weapon!);
|
||||
return new(reliquaryList, weapon);
|
||||
}
|
||||
|
||||
private PropertyWeapon CreateWeapon(Equip equip)
|
||||
|
||||
@@ -47,6 +47,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
|
||||
{
|
||||
ThreadHelper.InvokeOnMainThread(() =>
|
||||
{
|
||||
// Database items have been deleted by cascade deleting.
|
||||
entries?.RemoveWhere(n => n.UserId == message.RemovedUserId);
|
||||
});
|
||||
}
|
||||
@@ -151,6 +152,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
@@ -296,6 +297,7 @@ internal class GachaLogService : IGachaLogService
|
||||
break;
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
SaveGachaItems(itemsToAdd, isLazy, archive, configration.EndId);
|
||||
await RandomDelayAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
@@ -326,6 +328,8 @@ internal class GachaLogService : IGachaLogService
|
||||
GachaItem? item = null;
|
||||
|
||||
if (archive != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: replace with MaxBy
|
||||
// https://github.com/dotnet/efcore/issues/25566
|
||||
@@ -336,6 +340,11 @@ internal class GachaLogService : IGachaLogService
|
||||
.OrderByDescending(i => i.Id)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
catch (SqliteException ex)
|
||||
{
|
||||
throw new Core.ExceptionService.UserdataCorruptedException("无法获取祈愿记录 End Id", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return item?.Id ?? 0L;
|
||||
}
|
||||
@@ -369,11 +378,12 @@ internal class GachaLogService : IGachaLogService
|
||||
|
||||
private INameQuality GetNameQualityByItemId(int id)
|
||||
{
|
||||
return id.Place() switch
|
||||
int place = id.Place();
|
||||
return place switch
|
||||
{
|
||||
8 => idAvatarMap![id],
|
||||
5 => idWeaponMap![id],
|
||||
_ => throw Must.NeverHappen(),
|
||||
_ => throw Must.NeverHappen($"Id places: {place}"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,11 @@ internal class GameService : IGameService, IDisposable
|
||||
string gamePath = GetGamePathSkipLocator();
|
||||
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))
|
||||
{
|
||||
List<IniElement> elements = IniSerializer.Deserialize(stream).ToList();
|
||||
|
||||
@@ -18,14 +18,21 @@ public struct MultiChannel
|
||||
/// </summary>
|
||||
public string SubChannel;
|
||||
|
||||
/// <summary>
|
||||
/// 配置文件路径 当不为 null 时则存在文件读写问题
|
||||
/// </summary>
|
||||
public string? ConfigFilePath;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的多通道
|
||||
/// </summary>
|
||||
/// <param name="channel">通道</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;
|
||||
SubChannel = subChannel ?? string.Empty;
|
||||
ConfigFilePath = configFilePath;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Win32;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Diagnostics.ToolHelp;
|
||||
using static Windows.Win32.PInvoke;
|
||||
|
||||
@@ -75,21 +76,24 @@ internal class GameFpsUnlocker : IGameFpsUnlocker
|
||||
|
||||
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());
|
||||
|
||||
MODULEENTRY32 entry = StructMarshal.MODULEENTRY32();
|
||||
bool found = false;
|
||||
|
||||
// First module must be exe. Ignoring it.
|
||||
for (Module32First(snapshot, ref entry); Module32Next(snapshot, ref entry);)
|
||||
bool loop = Module32First(snapshot, &entry);
|
||||
while (loop)
|
||||
{
|
||||
if (entry.th32ProcessID == processId && entry.szModule.AsString() == moduleName)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
loop = Module32Next(snapshot, &entry);
|
||||
}
|
||||
|
||||
return found ? entry : default;
|
||||
|
||||
@@ -181,7 +181,7 @@ internal class NavigationService : INavigationService
|
||||
/// <inheritdoc/>
|
||||
public void GoBack()
|
||||
{
|
||||
Program.DispatcherQueue!.TryEnqueue(() =>
|
||||
ThreadHelper.InvokeOnMainThread(() =>
|
||||
{
|
||||
bool canGoBack = Frame?.CanGoBack ?? false;
|
||||
|
||||
|
||||
@@ -124,8 +124,15 @@ internal class UserService : IUserService
|
||||
}
|
||||
|
||||
userCollection = users.ToObservableCollection();
|
||||
try
|
||||
{
|
||||
Current = users.SingleOrDefault(user => user.IsSelected);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
throw new Core.ExceptionService.UserdataCorruptedException("无法设置当前用户", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return userCollection;
|
||||
}
|
||||
@@ -156,10 +163,18 @@ internal class UserService : IUserService
|
||||
public UserGameRole? GetUserGameRoleByUid(string uid)
|
||||
{
|
||||
if (userCollection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -37,9 +37,12 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Control\Panel\PanelSelector.xaml" />
|
||||
<None Remove="Control\Theme\FontStyle.xaml" />
|
||||
<None Remove="LaunchGameWindow.xaml" />
|
||||
<None Remove="NativeMethods.json" />
|
||||
<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_BagTabIcon_Avatar.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\StoreLogo.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_BagTabIcon_Avatar.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.UI.Behaviors" 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.Tools" Version="7.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -171,14 +175,14 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</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="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<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)" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -415,4 +419,9 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Control\Style\FontStyle.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -64,25 +64,18 @@
|
||||
|
||||
<DataTemplate x:Key="OrangeGridTemplate" x:DataType="shmbg:SummaryItem">
|
||||
<Grid Width="40" Margin="0,4,4,0">
|
||||
<Border
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
ToolTipService.ToolTip="{Binding TimeFormatted}">
|
||||
<Border Style="{StaticResource BorderCardStyle}" ToolTipService.ToolTip="{Binding TimeFormatted}">
|
||||
<StackPanel>
|
||||
<shvc:ItemIcon
|
||||
Width="40"
|
||||
Height="40"
|
||||
Icon="{Binding Icon}"
|
||||
Quality="QUALITY_ORANGE"/>
|
||||
<!--<shci:CachedImage
|
||||
Source="{Binding Icon}"
|
||||
Height="40" Width="40"/>-->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding LastPull}"
|
||||
TextTrimming="None"
|
||||
TextWrapping="NoWrap">
|
||||
<TextBlock.Foreground>
|
||||
<SolidColorBrush Color="{Binding Color}"/>
|
||||
@@ -94,7 +87,7 @@
|
||||
</DataTemplate>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border Background="{StaticResource CardBackgroundFillColorDefaultBrush}" CornerRadius="{StaticResource CompatCornerRadius}">
|
||||
<Border Style="{StaticResource BorderCardStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
@@ -108,19 +101,23 @@
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
BorderBrush="{x:Null}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="4,4,0,0"
|
||||
IsExpanded="True">
|
||||
<Expander.Resources>
|
||||
<Thickness x:Key="ExpanderHeaderBorderThickness">0,0,0,1</Thickness>
|
||||
</Expander.Resources>
|
||||
<Expander.Header>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding Name}"/>
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,4,12,2"
|
||||
VerticalAlignment="Bottom"
|
||||
FontFamily="Consolas"
|
||||
FontSize="24"
|
||||
Margin="0,0,6,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="20"
|
||||
Text="{Binding TotalCount}"
|
||||
Visibility="{Binding ElementName=DetailExpander, Path=IsExpanded, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
|
||||
<shcp:PanelSelector
|
||||
@@ -135,7 +132,7 @@
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,4,0,4"
|
||||
FontFamily="Consolas"
|
||||
FontFamily="{StaticResource CascadiaMonoAndMiSans}"
|
||||
FontSize="48"
|
||||
Text="{Binding TotalCount}"/>
|
||||
<TextBlock
|
||||
@@ -149,12 +146,7 @@
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}">
|
||||
<Border Grid.Column="0" Style="{StaticResource BorderCardStyle}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
@@ -165,14 +157,13 @@
|
||||
Width="40"
|
||||
Height="40"
|
||||
Margin="4"
|
||||
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
Foreground="{StaticResource OrangeBrush}"
|
||||
IsIndeterminate="False"
|
||||
Maximum="{Binding GuarenteeOrangeThreshold}"
|
||||
Value="{Binding LastOrangePull}"/>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource OrangeBrush}"
|
||||
@@ -180,7 +171,6 @@
|
||||
Text="{Binding LastOrangePull}"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource OrangeBrush}"
|
||||
@@ -188,12 +178,7 @@
|
||||
Text="五星"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
Background="{StaticResource CardBackgroundFillColorDefault}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}">
|
||||
<Border Grid.Column="1" Style="{StaticResource BorderCardStyle}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
@@ -204,14 +189,13 @@
|
||||
Width="40"
|
||||
Height="40"
|
||||
Margin="4"
|
||||
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
Foreground="{StaticResource PurpleBrush}"
|
||||
IsIndeterminate="False"
|
||||
Maximum="{Binding GuarenteePurpleThreshold}"
|
||||
Value="{Binding LastPurplePull}"/>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource PurpleBrush}"
|
||||
@@ -219,7 +203,6 @@
|
||||
Text="{Binding LastPurplePull}"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource PurpleBrush}"
|
||||
@@ -232,20 +215,17 @@
|
||||
<StackPanel Margin="0,8,0,0" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
FontFamily="Consolas"
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding FromFormatted}"/>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="Consolas"
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="-"/>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
FontFamily="Consolas"
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding ToFormatted}"/>
|
||||
@@ -260,7 +240,7 @@
|
||||
Text="五星"/>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Consolas"
|
||||
FontFamily="{StaticResource CascadiaMonoAndMiSans}"
|
||||
Foreground="{StaticResource OrangeBrush}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{Binding TotalOrangeFormatted}"/>
|
||||
@@ -272,7 +252,7 @@
|
||||
Text="四星"/>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Consolas"
|
||||
FontFamily="{StaticResource CascadiaMonoAndMiSans}"
|
||||
Foreground="{StaticResource PurpleBrush}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{Binding TotalPurpleFormatted}"/>
|
||||
@@ -284,7 +264,7 @@
|
||||
Text="三星"/>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Consolas"
|
||||
FontFamily="{StaticResource CascadiaMonoAndMiSans}"
|
||||
Foreground="{StaticResource BlueBrush}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{Binding TotalBlueFormatted}"/>
|
||||
@@ -294,7 +274,6 @@
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="五星平均抽数"/>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Consolas,MicroSoft YaHei UI"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{Binding AverageOrangePullFormatted}"/>
|
||||
</Grid>
|
||||
@@ -302,19 +281,16 @@
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="UP 平均抽数"/>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Consolas,MicroSoft YaHei UI"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{Binding AverageUpOrangePullFormatted}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,2,0,0">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
FontFamily="Consolas,MicroSoft YaHei UI"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{Binding MaxOrangePullFormatted}"/>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Consolas,MicroSoft YaHei UI"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{Binding MinOrangePullFormatted}"/>
|
||||
</Grid>
|
||||
|
||||
@@ -4,17 +4,18 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="米游社养成计算"
|
||||
Closed="OnContentDialogClosed"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonText="完成"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentControl.Resources>
|
||||
<Thickness x:Key="ContentDialogPadding">0</Thickness>
|
||||
<CornerRadius x:Key="OverlayCornerRadius">0</CornerRadius>
|
||||
</ContentControl.Resources>
|
||||
<Grid Loaded="OnGridLoaded">
|
||||
<WebView2
|
||||
Name="WebView"
|
||||
Width="380"
|
||||
Height="500"/>
|
||||
Height="500"
|
||||
AllowFocusOnInteraction="False"/>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -8,15 +8,17 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Closed="OnContentDialogClosed"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonText="完成"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentControl.Resources>
|
||||
<Thickness x:Key="ContentDialogPadding">0</Thickness>
|
||||
<CornerRadius x:Key="OverlayCornerRadius">0</CornerRadius>
|
||||
</ContentControl.Resources>
|
||||
<Grid Loaded="OnGridLoaded">
|
||||
<WebView2
|
||||
Name="WebView"
|
||||
Width="360"
|
||||
Height="580"/>
|
||||
Height="580"
|
||||
AllowFocusOnInteraction="True"/>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="米游社实时便笺"
|
||||
Closed="OnContentDialogClosed"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonText="完成"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentControl.Resources>
|
||||
<Thickness x:Key="ContentDialogPadding">0</Thickness>
|
||||
<CornerRadius x:Key="OverlayCornerRadius">0</CornerRadius>
|
||||
</ContentControl.Resources>
|
||||
<Grid Loaded="OnGridLoaded">
|
||||
<WebView2
|
||||
Name="WebView"
|
||||
Width="380"
|
||||
Height="448"/>
|
||||
Height="448"
|
||||
AllowFocusOnInteraction="False"
|
||||
DefaultBackgroundColor="Transparent"/>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -11,20 +11,19 @@ using Snap.Hutao.Web.Bridge;
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
/// <summary>
|
||||
/// ʵʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 实时便笺验证对话框
|
||||
/// </summary>
|
||||
public sealed partial class DailyNoteVerificationDialog : ContentDialog
|
||||
{
|
||||
private readonly IServiceScope scope;
|
||||
private readonly UserAndUid userAndUid;
|
||||
|
||||
[SuppressMessage("", "IDE0052")]
|
||||
private DailyNoteJsInterface? dailyNoteJsInterface;
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD>µ<EFBFBD>ʵʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD>Ի<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 构造一个新的实时便笺验证对话框
|
||||
/// </summary>
|
||||
/// <param name="userAndUid"><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ</param>
|
||||
/// <param name="userAndUid">用户与角色</param>
|
||||
public DailyNoteVerificationDialog(UserAndUid userAndUid)
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -46,13 +45,20 @@ public sealed partial class DailyNoteVerificationDialog : ContentDialog
|
||||
Model.Entity.User user = userAndUid.User;
|
||||
coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent();
|
||||
dailyNoteJsInterface = new(coreWebView2, scope.ServiceProvider);
|
||||
dailyNoteJsInterface.ClosePageRequested += OnClosePageRequested;
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
private void OnClosePageRequested()
|
||||
{
|
||||
ThreadHelper.InvokeOnMainThread(Hide);
|
||||
}
|
||||
|
||||
private void OnContentDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
|
||||
{
|
||||
dailyNoteJsInterface.ClosePageRequested -= OnClosePageRequested;
|
||||
dailyNoteJsInterface = null;
|
||||
scope.Dispose();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,6 +160,9 @@
|
||||
Margin="16,0,0,16"
|
||||
ItemsPanel="{StaticResource ItemsStackPanelTemplate}"
|
||||
ItemsSource="{Binding Achievements}">
|
||||
<ItemsControl.ItemContainerTransitions>
|
||||
<ContentThemeTransition/>
|
||||
</ItemsControl.ItemContainerTransitions>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid
|
||||
|
||||
@@ -6,6 +6,8 @@ using Microsoft.UI.Xaml.Navigation;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.View.Extension;
|
||||
using System.Diagnostics;
|
||||
using Windows.System;
|
||||
|
||||
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);";
|
||||
|
||||
// support click open browser.
|
||||
private const string MihoyoSDKDefinition =
|
||||
@"window.miHoYoGameJSSDK = {
|
||||
openInBrowser: function(url){ window.chrome.webview.postMessage(url); },
|
||||
openInWebview: function(url){ location.href = url }}";
|
||||
private const string MihoyoSDKDefinition = """
|
||||
window.miHoYoGameJSSDK = {
|
||||
openInBrowser: function(url){ window.chrome.webview.postMessage(url); },
|
||||
openInWebview: function(url){ location.href = url }
|
||||
}
|
||||
""";
|
||||
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawContent))
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Pivot>
|
||||
<Pivot Style="{StaticResource DefaultPivotStyle}">
|
||||
<PivotItem
|
||||
Content="{Binding Announcement.List[0]}"
|
||||
ContentTemplate="{StaticResource AnnouncementTemplate}"
|
||||
|
||||
@@ -203,8 +203,12 @@
|
||||
Margin="4,0,0,2"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#FFFFFFFF"
|
||||
HorizontalTextAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding SelectedAvatar.FetterLevel}"/>
|
||||
Text="{Binding SelectedAvatar.FetterLevel}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="None"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -481,7 +481,8 @@
|
||||
<wsc:Setting
|
||||
Description="使用当前账号的Cookie信息刷新祈愿记录"
|
||||
Header="Stoken 刷新"
|
||||
Icon="{shcm:FontIcon Glyph=}">
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Style="{StaticResource DefaultSettingStyle}">
|
||||
<Button Command="{Binding RefreshByStokenCommand}" Content="获取"/>
|
||||
</wsc:Setting>
|
||||
<wsc:Setting
|
||||
|
||||
@@ -468,7 +468,16 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive)
|
||||
{
|
||||
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.
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
|
||||
@@ -87,10 +87,16 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
|
||||
if (SetProperty(ref selectedRefreshTime, value))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
if (!ScheduleTaskHelper.RegisterForDailyNoteRefresh(value.Value))
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IInfoBarService>().Warning("注册计划任务失败,请使用管理员模式重试");
|
||||
}
|
||||
else
|
||||
{
|
||||
refreshSecondsEntry!.SetInt32(value.Value);
|
||||
appDbContext.Settings.UpdateAndSave(refreshSecondsEntry!);
|
||||
ScheduleTaskHelper.RegisterForDailyNoteRefresh(value.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,12 +176,19 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
|
||||
|
||||
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");
|
||||
selectedRefreshTime = refreshTimes.Single(t => t.Value == refreshSecondsEntry.GetInt32());
|
||||
ScheduleTaskHelper.RegisterForDailyNoteRefresh(480);
|
||||
OnPropertyChanged(nameof(SelectedRefreshTime));
|
||||
ScheduleTaskHelper.RegisterForDailyNoteRefresh(480);
|
||||
|
||||
reminderNotifyEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString);
|
||||
isReminderNotification = reminderNotifyEntry.GetBoolean();
|
||||
@@ -185,10 +198,17 @@ internal class DailyNoteViewModel : Abstraction.ViewModel
|
||||
isSilentWhenPlayingGame = silentModeEntry.GetBoolean();
|
||||
OnPropertyChanged(nameof(IsSilentWhenPlayingGame));
|
||||
|
||||
await ThreadHelper.SwitchToBackgroundAsync();
|
||||
}
|
||||
|
||||
ObservableCollection<DailyNoteEntry> temp = await dailyNoteService.GetDailyNoteEntriesAsync().ConfigureAwait(false);
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
DailyNoteEntries = temp;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TrackRoleAsync(UserAndUid? role)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core.IO;
|
||||
@@ -199,7 +200,26 @@ internal class GachaLogViewModel : Abstraction.ViewModel
|
||||
GachaLogRefreshProgressDialog dialog = new();
|
||||
IAsyncDisposable dialogHider = await dialog.BlockAsync().ConfigureAwait(false);
|
||||
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();
|
||||
if (authkeyValid)
|
||||
|
||||
@@ -221,13 +221,21 @@ internal class LaunchGameViewModel : Abstraction.ViewModel
|
||||
ThrowIfViewDisposed();
|
||||
|
||||
MultiChannel multi = gameService.GetMultiChannel();
|
||||
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);
|
||||
|
||||
// Sync 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
|
||||
|
||||
@@ -206,8 +206,17 @@ internal class SettingViewModel : Abstraction.ViewModel
|
||||
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
if (Directory.Exists(cacheFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(cacheFolder, true);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
infoBarService.Warning($"清除失败,文件目录权限不足,请使用管理员模式重试");
|
||||
return;
|
||||
}
|
||||
|
||||
infoBarService.Success("清除完成");
|
||||
}
|
||||
else
|
||||
|
||||
@@ -52,7 +52,42 @@ internal class WelcomeViewModel : ObservableObject
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
List<DownloadSummary> downloadSummaries = new();
|
||||
HashSet<DownloadSummary> downloadSummaries = GenerateStaticResourceDownloadTasks();
|
||||
|
||||
DownloadSummaries = downloadSummaries.ToObservableCollection();
|
||||
|
||||
// Cancel all previous created jobs
|
||||
serviceProvider.GetRequiredService<BitsManager>().CancelAllJobs();
|
||||
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());
|
||||
|
||||
// Complete StaticResourceContracts
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV1Contract, true);
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV2Contract, true);
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV3Contract, true);
|
||||
|
||||
try
|
||||
{
|
||||
new ToastContentBuilder()
|
||||
.AddText("下载完成")
|
||||
.AddText("现在可以开始使用胡桃了")
|
||||
.Show();
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// 0x803E0105
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<DownloadSummary> GenerateStaticResourceDownloadTasks()
|
||||
{
|
||||
HashSet<DownloadSummary> downloadSummaries = new(EqualityComparer<DownloadSummary>.Default);
|
||||
|
||||
if (!LocalSetting.Get(SettingKeys.StaticResourceV1Contract, false))
|
||||
{
|
||||
@@ -75,35 +110,20 @@ internal class WelcomeViewModel : ObservableObject
|
||||
downloadSummaries.Add(new(serviceProvider, "圣遗物图标", "RelicIcon"));
|
||||
}
|
||||
|
||||
DownloadSummaries = new(downloadSummaries);
|
||||
|
||||
// Cancel all previous created jobs
|
||||
serviceProvider.GetRequiredService<BitsManager>().CancelAllJobs();
|
||||
await Task.WhenAll(downloadSummaries.Select(d => d.DownloadAndExtractAsync())).ConfigureAwait(true);
|
||||
|
||||
serviceProvider.GetRequiredService<IMessenger>().Send(new Message.WelcomeStateCompleteMessage());
|
||||
|
||||
// Complete StaticResourceContracts
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV1Contract, true);
|
||||
LocalSetting.Set(SettingKeys.StaticResourceV2Contract, true);
|
||||
|
||||
try
|
||||
if (!LocalSetting.Get(SettingKeys.StaticResourceV3Contract, false))
|
||||
{
|
||||
new ToastContentBuilder()
|
||||
.AddText("下载完成")
|
||||
.AddText("现在可以开始使用胡桃了")
|
||||
.Show();
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// 0x803E0105
|
||||
downloadSummaries.Add(new(serviceProvider, "天赋图标更新", "Skill"));
|
||||
downloadSummaries.Add(new(serviceProvider, "命之座图标更新", "Talent"));
|
||||
}
|
||||
|
||||
return downloadSummaries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载信息
|
||||
/// </summary>
|
||||
public class DownloadSummary : ObservableObject
|
||||
[SuppressMessage("", "CA1067")]
|
||||
public class DownloadSummary : ObservableObject, IEquatable<DownloadSummary>
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly BitsManager bitsManager;
|
||||
@@ -145,6 +165,12 @@ internal class WelcomeViewModel : ObservableObject
|
||||
/// </summary>
|
||||
public double ProgressValue { get => progressValue; set => SetProperty(ref progressValue, value); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(DownloadSummary? other)
|
||||
{
|
||||
return fileName == other?.fileName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步下载并解压
|
||||
/// </summary>
|
||||
|
||||
@@ -211,25 +211,25 @@ internal class WikiAvatarViewModel : Abstraction.ViewModel
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ImmutableIntrinsics.AssociationTypes.Contains(value))
|
||||
if (IntrinsicImmutables.AssociationTypes.Contains(value))
|
||||
{
|
||||
keep = keep || avatar.FetterInfo.Association.GetDescriptionOrNull() == value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ImmutableIntrinsics.WeaponTypes.Contains(value))
|
||||
if (IntrinsicImmutables.WeaponTypes.Contains(value))
|
||||
{
|
||||
keep = keep || avatar.Weapon.GetDescriptionOrNull() == value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ImmutableIntrinsics.ItemQualities.Contains(value))
|
||||
if (IntrinsicImmutables.ItemQualities.Contains(value))
|
||||
{
|
||||
keep = keep || avatar.Quality.GetDescriptionOrNull() == value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ImmutableIntrinsics.BodyTypes.Contains(value))
|
||||
if (IntrinsicImmutables.BodyTypes.Contains(value))
|
||||
{
|
||||
keep = keep || avatar.Body.GetDescriptionOrNull() == value;
|
||||
continue;
|
||||
|
||||
@@ -203,19 +203,19 @@ internal class WikiWeaponViewModel : Abstraction.ViewModel
|
||||
{
|
||||
string value = segment.ToString();
|
||||
|
||||
if (ImmutableIntrinsics.WeaponTypes.Contains(value))
|
||||
if (IntrinsicImmutables.WeaponTypes.Contains(value))
|
||||
{
|
||||
keep = keep || weapon.WeaponType.GetDescriptionOrNull() == value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ImmutableIntrinsics.ItemQualities.Contains(value))
|
||||
if (IntrinsicImmutables.ItemQualities.Contains(value))
|
||||
{
|
||||
keep = keep || weapon.Quality.GetDescriptionOrNull() == value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ImmutableIntrinsics.FightProperties.Contains(value))
|
||||
if (IntrinsicImmutables.FightProperties.Contains(value))
|
||||
{
|
||||
keep = keep || weapon.Property.Properties.ElementAtOrDefault(1).GetDescriptionOrNull() == value;
|
||||
continue;
|
||||
|
||||
@@ -41,6 +41,7 @@ public class MiHoYoJSInterface
|
||||
private readonly CoreWebView2 webView;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ILogger<MiHoYoJSInterface> logger;
|
||||
private readonly SemaphoreSlim webMessageSemaphore = new(1);
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的调用桥
|
||||
@@ -59,6 +60,8 @@ public class MiHoYoJSInterface
|
||||
webView.NavigationStarting += OnNavigationStarting;
|
||||
}
|
||||
|
||||
public event Action? ClosePageRequested;
|
||||
|
||||
/// <summary>
|
||||
/// 获取ActionTicket
|
||||
/// </summary>
|
||||
@@ -160,7 +163,7 @@ public class MiHoYoJSInterface
|
||||
string q = param.Payload.GetQueryParam();
|
||||
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()
|
||||
{
|
||||
@@ -214,9 +217,12 @@ public class MiHoYoJSInterface
|
||||
cookieToken = cookieTokenResponse.Data.CookieToken;
|
||||
}
|
||||
|
||||
// Check null and create a new one to avoid System.NullReferenceException
|
||||
user.CookieToken ??= new();
|
||||
|
||||
// sync ui and database
|
||||
user.CookieToken![Cookie.COOKIE_TOKEN] = cookieToken!;
|
||||
Ioc.Default.GetRequiredService<AppDbContext>().Users.UpdateAndSave(user.Entity);
|
||||
user.CookieToken[Cookie.COOKIE_TOKEN] = cookieToken!;
|
||||
serviceProvider.GetRequiredService<AppDbContext>().Users.UpdateAndSave(user.Entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -234,6 +240,7 @@ public class MiHoYoJSInterface
|
||||
[JsMethod("closePage")]
|
||||
public virtual IJsResult? ClosePage(JsParam param)
|
||||
{
|
||||
ClosePageRequested?.Invoke();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -256,7 +263,7 @@ public class MiHoYoJSInterface
|
||||
[JsMethod("getStatusBarHeight")]
|
||||
public virtual JsResult<Dictionary<string, object>> GetStatusBarHeight(JsParam param)
|
||||
{
|
||||
return new() { Data = new() { { "statusBarHeight", 0 } } };
|
||||
return new() { Data = new() { ["statusBarHeight"] = 0 } };
|
||||
}
|
||||
|
||||
[JsMethod("pushPage")]
|
||||
@@ -352,6 +359,8 @@ public class MiHoYoJSInterface
|
||||
JsParam param = JsonSerializer.Deserialize<JsParam>(message)!;
|
||||
|
||||
logger.LogInformation("[OnMessage]\nMethod : {method}\nPayload : {payload}\nCallback: {callback}", param.Method, param.Payload, param.Callback);
|
||||
using (await webMessageSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
IJsResult? result = await TryGetJsResultFromJsParamAsync(param).ConfigureAwait(false);
|
||||
|
||||
if (result != null && param.Callback != null)
|
||||
@@ -359,6 +368,7 @@ public class MiHoYoJSInterface
|
||||
await ExecuteCallbackScriptAsync(param.Callback, result.ToString()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IJsResult?> TryGetJsResultFromJsParamAsync(JsParam param)
|
||||
{
|
||||
|
||||
@@ -45,7 +45,7 @@ public partial class Cookie
|
||||
{
|
||||
SortedDictionary<string, string> cookieMap = new();
|
||||
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)))
|
||||
{
|
||||
string name = parts[0].Trim();
|
||||
|
||||
@@ -15,8 +15,8 @@ public class Game
|
||||
public Package Latest { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 差异文件
|
||||
/// 相对于当前版本的之前版本的差异文件(非预下载)
|
||||
/// </summary>
|
||||
[JsonPropertyName("diffs")]
|
||||
public IList<DiffPackage> Diffs { get; set; } = default!;
|
||||
public List<DiffPackage> Diffs { get; set; } = default!;
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@ public class GameResource
|
||||
public Plugin Plugin { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 官网地址
|
||||
/// 官网地址 https://ys.mihoyo.com/launcher
|
||||
/// </summary>
|
||||
[JsonPropertyName("web_url")]
|
||||
public Uri WebUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 强制更新文件
|
||||
/// 强制更新文件 null
|
||||
/// </summary>
|
||||
[JsonPropertyName("force_update")]
|
||||
public object? ForceUpdate { get; set; }
|
||||
@@ -36,17 +36,23 @@ public class GameResource
|
||||
/// 预下载
|
||||
/// </summary>
|
||||
[JsonPropertyName("pre_download_game")]
|
||||
public object? PreDownloadGame { get; set; }
|
||||
public Game? PreDownloadGame { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 过期更新包
|
||||
/// </summary>
|
||||
[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>
|
||||
[JsonPropertyName("deprecated_files")]
|
||||
public List<LocalFile>? DeprecatedFiles { get; set; }
|
||||
public List<NameMd5>? DeprecatedFiles { get; set; }
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||
/// <summary>
|
||||
/// 资源文件
|
||||
/// </summary>
|
||||
public class LocalFile
|
||||
public class NameMd5
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件名称
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||
/// <summary>
|
||||
/// 最新客户端
|
||||
/// </summary>
|
||||
public class Package : DownloadFile
|
||||
public class Package : PathMd5
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称 空
|
||||
@@ -27,14 +27,22 @@ public class Package : DownloadFile
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
public long Size { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 主程序相对路径 YuanShen.exe
|
||||
/// </summary>
|
||||
public string Entry { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 语音包
|
||||
/// </summary>
|
||||
[JsonPropertyName("voice_packs")]
|
||||
public List<VoicePackage> VoicePacks { get; set; } = default!;
|
||||
|
||||
// We don't want to support
|
||||
// decompressed_path & segments
|
||||
|
||||
/// <summary>
|
||||
/// 包大小
|
||||
/// 包大小 bytes
|
||||
/// </summary>
|
||||
[JsonPropertyName("package_size")]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||
/// <summary>
|
||||
/// 下载的文件
|
||||
/// </summary>
|
||||
public class DownloadFile
|
||||
public class PathMd5
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载地址
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||
/// <summary>
|
||||
/// 插件项
|
||||
/// </summary>
|
||||
public class PluginItem : LocalFile
|
||||
public class PluginItem : NameMd5
|
||||
{
|
||||
/// <summary>
|
||||
/// 版本 一般为空
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||
/// <summary>
|
||||
/// 语音包
|
||||
/// </summary>
|
||||
public class VoicePackage : DownloadFile
|
||||
public class VoicePackage : PathMd5
|
||||
{
|
||||
/// <summary>
|
||||
/// 语音
|
||||
|
||||
@@ -51,7 +51,7 @@ internal class HomaClient
|
||||
public async Task<Response<bool>> CheckRecordUploadedAsync(PlayerUid uid, CancellationToken token = default)
|
||||
{
|
||||
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);
|
||||
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
@@ -67,7 +67,7 @@ internal class HomaClient
|
||||
public async Task<Response<RankInfo>> GetRankAsync(PlayerUid uid, CancellationToken token = default)
|
||||
{
|
||||
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);
|
||||
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
@@ -82,7 +82,7 @@ internal class HomaClient
|
||||
public async Task<Response<Overview>> GetOverviewAsync(CancellationToken token = default)
|
||||
{
|
||||
Response<Overview>? resp = await httpClient
|
||||
.GetFromJsonAsync<Response<Overview>>(HutaoEndpoints.StatisticsOverview, token)
|
||||
.TryCatchGetFromJsonAsync<Response<Overview>>(HutaoEndpoints.StatisticsOverview, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Windows.Graphics;
|
||||
using Windows.Win32.Foundation;
|
||||
using static Windows.Win32.PInvoke;
|
||||
|
||||
namespace Snap.Hutao.Win32;
|
||||
|
||||
@@ -40,4 +42,29 @@ internal static class StructExtension
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user