diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj index 0e4c1dd7..fd9da915 100644 --- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj +++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Snap.Hutao.SourceGeneration.csproj @@ -25,4 +25,4 @@ - + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/BindingProxy.cs b/src/Snap.Hutao/Snap.Hutao/Control/BindingProxy.cs index 5b56719f..5e64644e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/BindingProxy.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/BindingProxy.cs @@ -8,6 +8,8 @@ namespace Snap.Hutao.Control; /// /// 绑定探针 /// 用于处理特定情况下需要穿透数据上下文的工作 +/// DependencyObject will dispose inner ReferenceTracker in any time +/// when object is not used anymore. /// public class BindingProxy : DependencyObject { diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtension.cs similarity index 96% rename from src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs rename to src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtension.cs index efd4fced..5eaab145 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtension.cs @@ -8,7 +8,7 @@ namespace Snap.Hutao.Control.Extension; /// /// 对话框扩展 /// -internal static class ContentDialogExtensions +internal static class ContentDialogExtension { /// /// 阻止用户交互 diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs index ef236fac..ab14ff26 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs @@ -32,7 +32,7 @@ public class CachedImage : ImageEx try { - Verify.Operation(imageUri.Host != string.Empty, "无效的Uri"); + Verify.Operation(imageUri.Host != string.Empty, SH.ControlImageCachedImageInvalidResourceUri); string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // check token state to determine whether the operation should be canceled. diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs index ae71b53d..1c4c8d45 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using CommunityToolkit.WinUI.UI.Animations; +using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Composition; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Hosting; @@ -11,6 +12,7 @@ using Snap.Hutao.Extension; using Snap.Hutao.Service.Abstraction; using System.IO; using System.Net.Http; +using System.Reflection; using System.Runtime.InteropServices; namespace Snap.Hutao.Control.Image; @@ -24,7 +26,7 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control private static readonly DependencyProperty SourceProperty = Property.Depend(nameof(Source), default(Uri), OnSourceChanged); private static readonly ConcurrentCancellationTokenSource LoadingTokenSource = new(); - private readonly IImageCache imageCache; + private readonly IServiceProvider serviceProvider; private SpriteVisual? spriteVisual; private bool isShow = true; @@ -34,8 +36,15 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control /// public CompositionImage() { - imageCache = Ioc.Default.GetRequiredService(); + serviceProvider = Ioc.Default.GetRequiredService(); + + AllowFocusOnInteraction = false; + IsDoubleTapEnabled = false; + IsHitTestVisible = false; + IsHoldingEnabled = false; + IsRightTapEnabled = false; IsTabStop = false; + SizeChanged += OnSizeChanged; } @@ -86,11 +95,11 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control if (exception is HttpRequestException httpRequestException) { - infoBarService.Error(httpRequestException, $"GET {uri}"); + infoBarService.Error(httpRequestException, string.Format(SH.ControlImageCompositionImageHttpRequest, uri)); } else { - infoBarService.Error(exception, $"应用 {nameof(CompositionImage)} 的源时发生异常"); + infoBarService.Error(exception.GetBaseException(), SH.ControlImageCompositionImageSystemException); } } @@ -124,26 +133,20 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control if (uri != null) { - if (uri.Scheme == "ms-appx") - { - imageSurface = LoadedImageSurface.StartLoadFromUri(uri); - } - else - { - string storageFile = await imageCache.GetFileFromCacheAsync(uri).ConfigureAwait(true); + IImageCache imageCache = serviceProvider.GetRequiredService(); + string file = await imageCache.GetFileFromCacheAsync(uri).ConfigureAwait(true); - try - { - imageSurface = await LoadImageSurfaceAsync(storageFile, token).ConfigureAwait(true); - } - catch (COMException) - { - imageCache.Remove(uri.Enumerate()); - } - catch (IOException) - { - imageCache.Remove(uri.Enumerate()); - } + try + { + imageSurface = await LoadImageSurfaceAsync(file, token).ConfigureAwait(true); + } + catch (COMException) + { + imageCache.Remove(uri.Enumerate()); + } + catch (IOException) + { + imageCache.Remove(uri.Enumerate()); } if (imageSurface != null) diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs index 4e982a4b..e1760298 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs @@ -6,6 +6,7 @@ using Microsoft.UI; using Microsoft.UI.Composition; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; +using Snap.Hutao.Control.Theme; namespace Snap.Hutao.Control.Image; @@ -49,12 +50,7 @@ public class MonoChrome : CompositionImage private void SetBackgroundColor(CompositionColorBrush backgroundBrush) { - ApplicationTheme theme = ActualTheme switch - { - ElementTheme.Light => ApplicationTheme.Light, - ElementTheme.Dark => ApplicationTheme.Dark, - _ => Ioc.Default.GetRequiredService().RequestedTheme, - }; + ApplicationTheme theme = ThemeHelper.ElementToApplication(ActualTheme); backgroundBrush.Color = theme switch { diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Markup/ResourceStringExtension.cs b/src/Snap.Hutao/Snap.Hutao/Control/Markup/ResourceStringExtension.cs new file mode 100644 index 00000000..c7a7ca36 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Markup/ResourceStringExtension.cs @@ -0,0 +1,36 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Xaml.Markup; + +namespace Snap.Hutao.Control.Markup; + +/// +/// Xaml extension to return a value from resource file associated with a resource key +/// +[MarkupExtensionReturnType(ReturnType = typeof(string))] +public sealed class ResourceStringExtension : MarkupExtension +{ + /// + /// Gets or sets associated ID from resource strings. + /// + public string? Name { get; set; } + + /// + /// Gets a string value from resource file associated with a resource key. + /// + /// Resource key name. + /// A string value from resource file associated with a resource key. + public static string GetValue(string name) + { + // This function is needed to accomodate compiled function usage without second paramater, + // which doesn't work with optional values. + return SH.ResourceManager.GetString(name)!; + } + + /// + protected override object ProvideValue() + { + return GetValue(Name ?? string.Empty); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Panel/AspectRatio.cs b/src/Snap.Hutao/Snap.Hutao/Control/Panel/AspectRatio.cs index 71bcf6f1..b77d4be3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Panel/AspectRatio.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Panel/AspectRatio.cs @@ -9,8 +9,10 @@ namespace Snap.Hutao.Control.Panel; /// /// 纵横比控件 /// -internal class AspectRatio : Microsoft.UI.Xaml.Controls.ContentControl +internal class AspectRatio : Microsoft.UI.Xaml.Controls.Control { + private const double Epsilon = 2.2204460492503131e-016; + private static readonly DependencyProperty TargetWidthProperty = Property.Depend(nameof(TargetWidth), 1D); private static readonly DependencyProperty TargetHeightProperty = Property.Depend(nameof(TargetHeight), 1D); @@ -38,6 +40,11 @@ internal class AspectRatio : Microsoft.UI.Xaml.Controls.ContentControl double ratio = TargetWidth / TargetHeight; double ratioAvailable = availableSize.Width / availableSize.Height; + if (Math.Abs(ratioAvailable - ratio) < Epsilon) + { + return availableSize; + } + // 更宽 if (ratioAvailable > ratio) { diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml index 24939cfb..0cf5046b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml @@ -21,12 +21,12 @@ Click="RadioMenuFlyoutItemClick" Icon="{shcm:FontIcon Glyph=}" Tag="List" - Text="列表"/> + Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/> + Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/> diff --git a/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs b/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs index 6be2322a..b2ef6480 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs @@ -16,16 +16,34 @@ namespace Snap.Hutao.Control; [SuppressMessage("", "CA1001")] public class ScopedPage : Page { + // Allow GC to Collect the IServiceScope + private static readonly WeakReference PreviousScopeReference = new(null!); + private readonly CancellationTokenSource viewCancellationTokenSource = new(); - private readonly IServiceScope serviceScope; + private readonly IServiceScope currentScope; /// /// 构造一个新的页面 /// public ScopedPage() { - serviceScope = Ioc.Default.CreateScope(); - serviceScope.Track(); + Unloaded += OnScopedPageUnloaded; + currentScope = Ioc.Default.CreateScope(); + DisposePreviousScope(); + + // track current + PreviousScopeReference.SetTarget(currentScope); + } + + /// + /// 释放上个范围 + /// + public static void DisposePreviousScope() + { + if (PreviousScopeReference.TryGetTarget(out IServiceScope? scope)) + { + scope.Dispose(); + } } /// @@ -36,7 +54,7 @@ public class ScopedPage : Page public void InitializeWith() where TViewModel : class, IViewModel { - IViewModel viewModel = serviceScope.ServiceProvider.GetRequiredService(); + IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService(); viewModel.CancellationToken = viewCancellationTokenSource.Token; DataContext = viewModel; } @@ -59,7 +77,6 @@ public class ScopedPage : Page /// protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) { - base.OnNavigatingFrom(e); using (viewCancellationTokenSource) { // Cancel all tasks executed by the view model @@ -73,7 +90,7 @@ public class ScopedPage : Page viewModel.IsViewDisposed = true; // Dispose the scope - serviceScope.Dispose(); + currentScope.Dispose(); } } } @@ -81,11 +98,15 @@ public class ScopedPage : Page /// protected override void OnNavigatedTo(NavigationEventArgs e) { - base.OnNavigatedTo(e); - if (e.Parameter is INavigationData extra) { NotifyRecipentAsync(extra).SafeForget(); } } + + private void OnScopedPageUnloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + { + DataContext = null; + Unloaded -= OnScopedPageUnloaded; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs index ea1e0ffe..35d69c02 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs @@ -9,7 +9,7 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Documents; using Microsoft.UI.Xaml.Media; using Snap.Hutao.Control.Media; -using Snap.Hutao.Core; +using Snap.Hutao.Control.Theme; using Windows.UI; namespace Snap.Hutao.Control.Text; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ThemeHelper.cs b/src/Snap.Hutao/Snap.Hutao/Control/Theme/ThemeHelper.cs similarity index 82% rename from src/Snap.Hutao/Snap.Hutao/Core/ThemeHelper.cs rename to src/Snap.Hutao/Snap.Hutao/Control/Theme/ThemeHelper.cs index 8d5d09fe..d750f6f6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ThemeHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/ThemeHelper.cs @@ -4,7 +4,7 @@ using Microsoft.UI.Composition.SystemBackdrops; using Microsoft.UI.Xaml; -namespace Snap.Hutao.Core; +namespace Snap.Hutao.Control.Theme; /// /// 主题帮助工具类 @@ -42,6 +42,21 @@ public static class ThemeHelper }; } + /// + /// 从 转换到 + /// + /// 元素主题 + /// 应用主题 + public static ApplicationTheme ElementToApplication(ElementTheme applicationTheme) + { + return applicationTheme switch + { + ElementTheme.Light => ApplicationTheme.Light, + ElementTheme.Dark => ApplicationTheme.Dark, + _ => Ioc.Default.GetRequiredService().RequestedTheme, + }; + } + /// /// 从 转换到 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs index b64e09b1..fb5f90cc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs @@ -70,6 +70,11 @@ internal static class CoreEnvironment /// public static readonly string FamilyName; + /// + /// 安装位置 + /// + public static readonly string InstalledLocation; + /// /// 数据文件夹 /// @@ -98,9 +103,10 @@ internal static class CoreEnvironment static CoreEnvironment() { - DataFolder = GetDocumentsHutaoPath(); + DataFolder = GetDatafolderPath(); Version = Package.Current.Id.Version.ToVersion(); FamilyName = Package.Current.Id.FamilyName; + InstalledLocation = Package.Current.InstalledLocation.Path; CommonUA = $"Snap Hutao/{Version}"; // simply assign a random guid @@ -115,7 +121,7 @@ internal static class CoreEnvironment return Convert.ToMd5HexString($"{userName}{machineGuid}"); } - private static string GetDocumentsHutaoPath() + private static string GetDatafolderPath() { string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); #if RELEASE diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs index ca944b11..67ad19d0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/IocConfiguration.cs @@ -18,7 +18,7 @@ internal static class IocConfiguration /// /// 集合 /// 可继续操作的集合 - public static IServiceCollection AddJsonSerializerOptions(this IServiceCollection services) + public static IServiceCollection AddJsonOptions(this IServiceCollection services) { return services.AddSingleton(CoreEnvironment.JsonOptions); } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceScopeExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceScopeExtension.cs deleted file mode 100644 index f52fd970..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceScopeExtension.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Extensions.DependencyInjection; - -namespace Snap.Hutao.Core.DependencyInjection; - -/// -/// 服务范围扩展 -/// -public static class ServiceScopeExtension -{ - // Allow GC to Collect the IServiceScope - private static readonly WeakReference ScopeReference = new(null!); - - /// - /// 追踪服务范围 - /// - /// 范围 - public static void Track(this IServiceScope scope) - { - DisposeLast(); - ScopeReference.SetTarget(scope); - } - - /// - /// 释放上个范围 - /// - public static void DisposeLast() - { - if (ScopeReference.TryGetTarget(out IServiceScope? scope)) - { - scope.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs new file mode 100644 index 00000000..426f0225 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs @@ -0,0 +1,56 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Service.Game.Package; +using System.Runtime.CompilerServices; + +namespace Snap.Hutao.Core.ExceptionService; + +/// +/// 帮助更好的抛出异常 +/// +[System.Diagnostics.StackTraceHidden] +internal static class ThrowHelper +{ + /// + /// 操作取消 + /// + /// 消息 + /// 内部错误 + /// 操作取消异常 + /// nothing + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static OperationCanceledException OperationCanceled(string message, Exception? inner) + { + throw new OperationCanceledException(message, inner); + } + + /// + /// 包转换错误 + /// + /// 消息 + /// 内部错误 + /// nothing + /// 包转换错误异常 + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static PackageConvertException PackageConvert(string message, Exception inner) + { + throw new PackageConvertException(message, inner); + } + + /// + /// 用户数据损坏 + /// + /// 消息 + /// 内部错误 + /// 数据损坏 + /// nothing + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static UserdataCorruptedException UserdataCorrupted(string message, Exception inner) + { + throw new UserdataCorruptedException(message, inner); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs index bb4c8c31..217b3af7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs @@ -14,7 +14,7 @@ internal class UserdataCorruptedException : Exception /// 消息 /// 内部错误 public UserdataCorruptedException(string message, Exception innerException) - : base($"用户数据已损坏: {message}", innerException) + : base(string.Format(SH.CoreExceptionServiceUserdataCorruptedMessage, message), innerException) { } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/BitsJob.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/BitsJob.cs index 47d6db7f..df436b5d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/BitsJob.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/BitsJob.cs @@ -30,7 +30,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback private readonly object lockObj = new(); private IBackgroundCopyJob? nativeJob; - private System.Exception? jobException; + private Exception? jobException; private BG_JOB_PROGRESS progress; private BG_JOB_STATE state; private bool isJobComplete; @@ -79,7 +79,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback UpdateJobState(); CompleteOrCancel(); } - catch (System.Exception ex) + catch (Exception ex) { log.LogInformation("Failed to job transfer: {message}", ex.Message); } @@ -101,7 +101,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback CompleteOrCancel(); log.LogInformation(jobException, "Job Exception:"); } - catch (System.Exception ex) + catch (Exception ex) { log?.LogInformation("Failed to handle job error: {message}", ex.Message); } @@ -141,7 +141,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback CompleteOrCancel(); } } - catch (System.Exception ex) + catch (Exception ex) { log.LogInformation(ex, "message"); } @@ -283,7 +283,7 @@ internal class BitsJob : DisposableObject, IBackgroundCopyCallback { action(); } - catch (System.Exception ex) + catch (Exception ex) { log.LogInformation("{name} failed. {exception}", displayName, ex); if (throwOnFailure) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/BitsManager.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/BitsManager.cs index 09f74588..af8c2ba7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/BitsManager.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/BitsManager.cs @@ -39,7 +39,8 @@ internal class BitsManager public async Task> DownloadAsync(Uri uri, IProgress progress, CancellationToken token = default) { TempFile tempFile = new(true); - bool result = await Task.Run(() => DownloadCore(uri, tempFile.Path, progress.Report, token), token).ConfigureAwait(false); + await ThreadHelper.SwitchToBackgroundAsync(); + bool result = DownloadCore(uri, tempFile.Path, progress.Report, token); return new(result, tempFile); } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/ProgressUpdateStatus.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/ProgressUpdateStatus.cs index 8c3ec78f..f8ab9af3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/ProgressUpdateStatus.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Bits/ProgressUpdateStatus.cs @@ -1,11 +1,14 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using System.Diagnostics; + namespace Snap.Hutao.Core.IO.Bits; /// /// 进度更新状态 /// +[DebuggerDisplay("{BytesRead}/{TotalBytes}")] public class ProgressUpdateStatus { /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs index 497733c5..ab54c4c5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/DataTransfer/Clipboard.cs @@ -20,7 +20,6 @@ internal static class Clipboard public static async Task DeserializeTextAsync(JsonSerializerOptions options) where T : class { - await ThreadHelper.SwitchToMainThreadAsync(); DataPackageView view = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent(); string json = await view.GetTextAsync(); return JsonSerializer.Deserialize(json, options); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/PickerExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/PickerExtension.cs index 33940299..0f0e734a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/PickerExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/PickerExtension.cs @@ -32,13 +32,7 @@ internal static class PickerExtension } else { - if (exception != null) - { - Ioc.Default - .GetRequiredService() - .Warning("无法打开文件选择器", $"请勿在管理员模式下使用此功能 {exception.Message}"); - } - + InfoBarWaringPickerException(exception); return new(false, null!); } } @@ -64,14 +58,20 @@ internal static class PickerExtension } else { - if (exception != null) - { - Ioc.Default - .GetRequiredService() - .Warning("无法打开文件选择器", $"请勿在管理员模式下使用此功能 {exception.Message}"); - } - + InfoBarWaringPickerException(exception); return new(false, null!); } } + + private static void InfoBarWaringPickerException(Exception? exception) + { + if (exception != null) + { + Ioc.Default + .GetRequiredService() + .Warning( + SH.CoreIOPickerExtensionPickerExceptionInfoBarTitle, + string.Format(SH.CoreIOPickerExtensionPickerExceptionInfoBarMessage, exception.Message)); + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs index 9dab2b4e..f14a1daa 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs @@ -23,8 +23,8 @@ public static class JumpListHelper list.Items.Clear(); - JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, "启动游戏"); - launchGameItem.GroupName = "快捷操作"; + JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, SH.CoreJumpListHelperLaunchGameItemDisplayName); + launchGameItem.GroupName = SH.CoreJumpListHelperLaunchGameItemGroupName; launchGameItem.Logo = new("ms-appx:///Resource/Icon/UI_GuideIcon_PlayMethod.png"); list.Items.Add(launchGameItem); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatabaseLoggerFactoryExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatabaseLoggerFactoryExtensions.cs deleted file mode 100644 index 91864ecd..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatabaseLoggerFactoryExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace Snap.Hutao.Core.Logging; - -/// -/// Extension methods for the class. -/// -public static class DatabaseLoggerFactoryExtensions -{ - /// - /// Adds a debug logger named 'Debug' to the factory. - /// - /// The extension method argument. - /// 日志构造器 - public static ILoggingBuilder AddDatabase(this ILoggingBuilder builder) - { - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); - return builder; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs deleted file mode 100644 index a6c9b3f3..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Core.Logging; - -/// -/// A logger that writes messages in the database table -/// -internal sealed partial class DatebaseLogger : ILogger -{ - private readonly string name; - private readonly LogEntryQueue logEntryQueue; - - /// - /// Initializes a new instance of the class. - /// - /// The name of the logger. - /// 日志队列 - public DatebaseLogger(string name, LogEntryQueue logEntryQueue) - { - this.name = name; - this.logEntryQueue = logEntryQueue; - } - - /// - public IDisposable BeginScope(TState state) - where TState : notnull - { - return new NullScope(); - } - - /// - public bool IsEnabled(LogLevel logLevel) - { - return logLevel != LogLevel.None; - } - - /// - public void Log(LogLevel logLevel, EventId eventId, TState state, System.Exception? exception, Func formatter) - { - if (!IsEnabled(logLevel)) - { - return; - } - - string message = formatter(state, exception); - - if (string.IsNullOrEmpty(message)) - { - return; - } - - LogEntry entry = new() - { - Time = DateTimeOffset.Now, - Category = name, - LogLevel = logLevel, - EventId = eventId.Id, - Message = message, - Exception = exception?.ToString(), - }; - - logEntryQueue.Enqueue(entry); - } - - /// - /// An empty scope without any logic - /// - private struct NullScope : IDisposable - { - public NullScope() - { - } - - /// - public void Dispose() - { - } - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLoggerProvider.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLoggerProvider.cs deleted file mode 100644 index 3aabd840..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLoggerProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Core.Logging; - -/// -/// The provider for the . -/// -[ProviderAlias("Database")] -public sealed class DatebaseLoggerProvider : ILoggerProvider -{ - private readonly LogEntryQueue logEntryQueue = new(); - - /// - public ILogger CreateLogger(string name) - { - return new DatebaseLogger(name, logEntryQueue); - } - - /// - public void Dispose() - { - logEntryQueue.Dispose(); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogEntry.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogEntry.cs deleted file mode 100644 index 6c43298f..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogEntry.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Snap.Hutao.Core.Logging; - -/// -/// 数据库日志入口点 -/// -[Table("logs")] -public class LogEntry -{ - /// - /// 内部Id - /// - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public Guid InnerId { get; set; } - - /// - /// 日志时间 - /// - public DateTimeOffset Time { get; set; } - - /// - /// 类别 - /// - public string Category { get; set; } = default!; - - /// - /// 日志等级 - /// - public LogLevel LogLevel { get; set; } - - /// - /// 事件Id - /// - public int EventId { get; set; } - - /// - /// 消息 - /// - public string Message { get; set; } = default!; - - /// - /// 可能的异常 - /// - public string? Exception { get; set; } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogEntryQueue.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogEntryQueue.cs deleted file mode 100644 index 64468ddc..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogEntryQueue.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.EntityFrameworkCore; -using Snap.Hutao.Model.Entity.Database; -using System.Collections.Concurrent; -using System.Diagnostics; - -namespace Snap.Hutao.Core.Logging; - -/// -/// 日志队列 -/// -public sealed class LogEntryQueue : IDisposable -{ - private readonly ConcurrentQueue entryQueue = new(); - private readonly CancellationTokenSource disposeTokenSource = new(); - private readonly TaskCompletionSource writeDbCompletionSource = new(); - private readonly LogDbContext logDbContext; - - private bool disposed; - - /// - /// 构造一个新的日志队列 - /// - public LogEntryQueue() - { - logDbContext = InitializeDbContext(); - - Task.Run(() => WritePendingLogsAsync(disposeTokenSource.Token)).SafeForget(); - } - - /// - /// 将日志消息存入队列 - /// - /// 日志 - public void Enqueue(LogEntry logEntry) - { - entryQueue.Enqueue(logEntry); - } - - /// - [SuppressMessage("", "VSTHRD002")] - public void Dispose() - { - if (disposed) - { - return; - } - - // notify the write task to complete. - disposeTokenSource.Cancel(); - - // Wait the db operation complete. - writeDbCompletionSource.Task.GetAwaiter().GetResult(); - - logDbContext.Dispose(); - disposed = true; - } - - private static LogDbContext InitializeDbContext() - { - string logDbName = System.IO.Path.Combine(CoreEnvironment.DataFolder, "Log.db"); - LogDbContext logDbContext = LogDbContext.Create($"Data Source={logDbName}"); - if (logDbContext.Database.GetPendingMigrations().Any()) - { - Debug.WriteLine("[Debug] Performing LogDbContext Migrations"); - logDbContext.Database.Migrate(); - } - - // only raw sql can pass - logDbContext.Logs.Where(log => log.Exception == null).ExecuteDelete(); - return logDbContext; - } - - private async Task WritePendingLogsAsync(CancellationToken token) - { - bool hasAdded = false; - while (true) - { - if (entryQueue.TryDequeue(out LogEntry? logEntry)) - { - logDbContext.Logs.Add(logEntry); - hasAdded = true; - } - else - { - if (hasAdded) - { - logDbContext.SaveChanges(); - hasAdded = false; - } - - if (token.IsCancellationRequested) - { - writeDbCompletionSource.TrySetResult(); - break; - } - - try - { - await Task.Delay(5000, token).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - } - } - } - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ScheduleTaskHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ScheduleTaskHelper.cs index 6256358d..3ea99141 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ScheduleTaskHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ScheduleTaskHelper.cs @@ -31,7 +31,7 @@ internal static class ScheduleTaskHelper } TaskDefinition task = TaskService.Instance.NewTask(); - task.RegistrationInfo.Description = "胡桃实时便笺刷新任务 | 请勿编辑或删除。"; + task.RegistrationInfo.Description = SH.CoreScheduleTaskHelperDailyNoteRefreshTaskDescription; task.Triggers.Add(new TimeTrigger() { Repetition = new(TimeSpan.FromSeconds(interval), TimeSpan.Zero), }); task.Actions.Add("explorer", "hutao://DailyNote/Refresh"); TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/SemaphoreSlimExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/SemaphoreSlimExtension.cs similarity index 86% rename from src/Snap.Hutao/Snap.Hutao/Core/Threading/SemaphoreSlimExtensions.cs rename to src/Snap.Hutao/Snap.Hutao/Core/Threading/SemaphoreSlimExtension.cs index 9a7a17b4..82cd2ef0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/SemaphoreSlimExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/SemaphoreSlimExtension.cs @@ -1,12 +1,14 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.ExceptionService; + namespace Snap.Hutao.Core.Threading; /// /// 信号量扩展 /// -public static class SemaphoreSlimExtensions +public static class SemaphoreSlimExtension { /// /// 异步进入信号量 @@ -22,7 +24,7 @@ public static class SemaphoreSlimExtensions } catch (ObjectDisposedException ex) { - throw new OperationCanceledException("信号量已经被释放,操作取消", ex); + ThrowHelper.OperationCanceled(SH.CoreThreadingSemaphoreSlimDisposed, ex); } return new SemaphoreSlimReleaser(semaphoreSlim); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtensions.cs index 317f8a94..0185acc3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskExtensions.cs @@ -22,7 +22,7 @@ public static class TaskExtensions { await task.ConfigureAwait(false); } - catch (System.Exception ex) + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } @@ -39,11 +39,11 @@ public static class TaskExtensions { await task.ConfigureAwait(false); } - catch (TaskCanceledException) + catch (OperationCanceledException) { // Do nothing } - catch (System.Exception e) + catch (Exception e) { logger?.LogError(EventIds.TaskException, e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException()); } @@ -55,17 +55,17 @@ public static class TaskExtensions /// 任务 /// 日志器 /// 发生异常时调用 - public static async void SafeForget(this Task task, ILogger? logger = null, Action? onException = null) + public static async void SafeForget(this Task task, ILogger? logger = null, Action? onException = null) { try { await task.ConfigureAwait(false); } - catch (TaskCanceledException) + catch (OperationCanceledException) { // Do nothing } - catch (System.Exception e) + catch (Exception e) { logger?.LogError(EventIds.TaskException, e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException()); onException?.Invoke(e); @@ -79,17 +79,17 @@ public static class TaskExtensions /// 日志器 /// 任务取消时调用 /// 发生异常时调用 - public static async void SafeForget(this Task task, ILogger? logger = null, Action? onCanceled = null, Action? onException = null) + public static async void SafeForget(this Task task, ILogger? logger = null, Action? onCanceled = null, Action? onException = null) { try { await task.ConfigureAwait(false); } - catch (TaskCanceledException) + catch (OperationCanceledException) { onCanceled?.Invoke(); } - catch (System.Exception e) + catch (Exception e) { logger?.LogError(EventIds.TaskException, e, "{caller}:\r\n{exception}", nameof(SafeForget), e.GetBaseException()); onException?.Invoke(e); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadPoolSwitchOperation.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadPoolSwitchOperation.cs index 5d8ee0c7..cf2551c2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadPoolSwitchOperation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadPoolSwitchOperation.cs @@ -30,13 +30,13 @@ public readonly struct ThreadPoolSwitchOperation : IAwaitable public void OnCompleted(Action continuation) { - QueueContinuation(continuation, flowContext: true); + QueueContinuation(continuation, true); } /// public void UnsafeOnCompleted(Action continuation) { - QueueContinuation(continuation, flowContext: false); + QueueContinuation(continuation, false); } private static void QueueContinuation(Action continuation, bool flowContext) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs index 429cabcf..9fa90389 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs @@ -44,7 +44,7 @@ public static class Must /// 上下文 /// Nothing. This method always throws. [DoesNotReturn] - public static System.Exception NeverHappen(string? context = null) + public static Exception NeverHappen(string? context = null) { throw new NotSupportedException(context); } @@ -62,21 +62,4 @@ public static class Must { return value ?? throw new ArgumentNullException(parameterName); } - - /// - /// Throws an if the specified parameter's value is IntPtr.Zero. - /// - /// The value of the argument. - /// The name of the parameter to include in any thrown exception. - /// The value of the parameter. - /// Thrown if is . - public static Windows.Win32.Foundation.HWND NotNull(Windows.Win32.Foundation.HWND value, [CallerArgumentExpression("value")] string? parameterName = null) - { - if (value == default) - { - throw new ArgumentNullException(parameterName); - } - - return value; - } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs b/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs index 1b192f5f..bca6f18d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs @@ -16,7 +16,7 @@ internal abstract class WebView2Helper { private static bool hasEverDetected; private static bool isSupported; - private static string version = "未检测到 WebView2 运行时"; + private static string version = SH.CoreWebView2HelperVersionUndetected; /// /// 检测 WebView2 是否存在 @@ -36,7 +36,7 @@ internal abstract class WebView2Helper catch (FileNotFoundException ex) { ILogger logger = Ioc.Default.GetRequiredService>(); - logger.LogError(EventIds.WebView2EnvironmentException, ex, "WebView2 运行时未安装"); + logger.LogError(EventIds.WebView2EnvironmentException, ex, "WebView2 Runtime not installed."); isSupported = false; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs index d84e756d..92cfb10f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs @@ -124,8 +124,8 @@ internal sealed class ExtendedWindow : IRecipient /// 确保系统调度队列控制器存在 /// - public void Ensure() + public unsafe void Ensure() { if (DispatcherQueue.GetForCurrentThread() != null) { @@ -152,7 +153,7 @@ public class SystemBackdrop { DispatcherQueueOptions options = new() { - DwSize = Marshal.SizeOf(), + DwSize = sizeof(DispatcherQueueOptions), ThreadType = 2, // DQTYPE_THREAD_CURRENT ApartmentType = 2, // DQTAT_COM_STA }; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs index 2295cadc..a0d45100 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclassManager.cs @@ -37,7 +37,7 @@ internal class WindowSubclassManager : IDisposable public WindowSubclassManager(TWindow window, HWND hwnd, bool isLegacyDragBar) { this.window = window; - this.hwnd = Must.NotNull(hwnd); + this.hwnd = hwnd; this.isLegacyDragBar = isLegacyDragBar; } @@ -45,7 +45,7 @@ internal class WindowSubclassManager : IDisposable /// 尝试设置窗体子类 /// /// 是否设置成功 - public bool TrySetWindowSubclass() + public unsafe bool TrySetWindowSubclass() { windowProc = new(OnSubclassProcedure); bool windowHooked = SetWindowSubclass(hwnd, windowProc, WindowSubclassId, 0); diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs index f939b0d2..41aca282 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/DateTimeOffsetExtension.cs @@ -8,22 +8,6 @@ namespace Snap.Hutao.Extension; /// public static class DateTimeOffsetExtension { - /// - /// Converts the current to a that represents the local time. - /// - /// 时间偏移 - /// 保留主时间部分 - /// A that represents the local time. - public static DateTimeOffset ToLocalTime(this DateTimeOffset dateTimeOffset, bool keepTicks) - { - if (keepTicks) - { - dateTimeOffset -= TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now); - } - - return dateTimeOffset.ToLocalTime(); - } - /// /// 从Unix时间戳转换 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/LoggerExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/LoggerExtension.cs deleted file mode 100644 index 4db7cc49..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Extension/LoggerExtension.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Extension; - -/// -/// 日志器扩展 -/// -[SuppressMessage("", "CA2254")] -public static class LoggerExtension -{ - /// - public static T LogWarning(this ILogger logger, string message, params object?[] param) - { - logger.LogWarning(message, param); - return default!; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/StringBuilderExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/StringBuilderExtensions.cs index 44444b05..dfa068fb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/StringBuilderExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/StringBuilderExtensions.cs @@ -33,17 +33,4 @@ public static class StringBuilderExtensions { return condition ? sb.Append(value) : sb; } - - /// - /// 当条件符合时执行 - /// - /// 字符串建造器 - /// 条件 - /// 条件符合时附加的字符串 - /// 条件不符合时附加的字符串 - /// 同一个字符串建造器 - public static StringBuilder AppendIfElse(this StringBuilder sb, bool condition, string? trueValue, string? falseValue) - { - return condition ? sb.Append(trueValue) : sb.Append(falseValue); - } } diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/ContentDialogFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/ContentDialogFactory.cs index b82941f4..fb7b2605 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/ContentDialogFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/ContentDialogFactory.cs @@ -60,7 +60,7 @@ internal class ContentDialogFactory : IContentDialogFactory Title = title, Content = content, DefaultButton = ContentDialogButton.Primary, - PrimaryButtonText = "确认", + PrimaryButtonText = SH.FactoryContentDialogFactoryConfirmPrimaryButtonText, }; return dialog; @@ -75,8 +75,8 @@ internal class ContentDialogFactory : IContentDialogFactory Title = title, Content = content, DefaultButton = defaultButton, - PrimaryButtonText = "确认", - CloseButtonText = "取消", + PrimaryButtonText = SH.FactoryContentDialogFactoryConfirmPrimaryButtonText, + CloseButtonText = SH.FactoryContentDialogFactoryCancelCloseButtonText, }; return dialog; diff --git a/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs b/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs index ff431253..0d09504f 100644 --- a/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs +++ b/src/Snap.Hutao/Snap.Hutao/GlobalUsing.cs @@ -13,6 +13,7 @@ global using Snap.Hutao.Core.DependencyInjection; global using Snap.Hutao.Core.DependencyInjection.Annotation; global using Snap.Hutao.Core.Threading; global using Snap.Hutao.Core.Validation; +global using Snap.Hutao.Resource.Localization; // Runtime global using System; diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220720121521_Logs.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220720121521_Logs.Designer.cs deleted file mode 100644 index 94414f13..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220720121521_Logs.Designer.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Snap.Hutao.Model.Entity.Database; - -#nullable disable - -namespace Snap.Hutao.Migrations.LogDb -{ - [DbContext(typeof(LogDbContext))] - [Migration("20220720121521_Logs")] - partial class Logs - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.7"); - - modelBuilder.Entity("Snap.Hutao.Core.Logging.LogEntry", b => - { - b.Property("InnerId") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Category") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EventId") - .HasColumnType("INTEGER"); - - b.Property("Exception") - .HasColumnType("TEXT"); - - b.Property("LogLevel") - .HasColumnType("INTEGER"); - - b.Property("Message") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("InnerId"); - - b.ToTable("logs"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220720121521_Logs.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220720121521_Logs.cs deleted file mode 100644 index e5e68191..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220720121521_Logs.cs +++ /dev/null @@ -1,35 +0,0 @@ -// -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Snap.Hutao.Migrations.LogDb -{ - public partial class Logs : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "logs", - columns: table => new - { - InnerId = table.Column(type: "TEXT", nullable: false), - Category = table.Column(type: "TEXT", nullable: false), - LogLevel = table.Column(type: "INTEGER", nullable: false), - EventId = table.Column(type: "INTEGER", nullable: false), - Message = table.Column(type: "TEXT", nullable: false), - Exception = table.Column(type: "TEXT", nullable: true), - }, - constraints: table => - { - table.PrimaryKey("PK_logs", x => x.InnerId); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "logs"); - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220903071033_LogTime.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220903071033_LogTime.Designer.cs deleted file mode 100644 index 4ff3e508..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220903071033_LogTime.Designer.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Snap.Hutao.Model.Entity.Database; - -#nullable disable - -namespace Snap.Hutao.Migrations.LogDb -{ - [DbContext(typeof(LogDbContext))] - [Migration("20220903071033_LogTime")] - partial class LogTime - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.8"); - - modelBuilder.Entity("Snap.Hutao.Core.Logging.LogEntry", b => - { - b.Property("InnerId") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Category") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EventId") - .HasColumnType("INTEGER"); - - b.Property("Exception") - .HasColumnType("TEXT"); - - b.Property("LogLevel") - .HasColumnType("INTEGER"); - - b.Property("Message") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Time") - .HasColumnType("TEXT"); - - b.HasKey("InnerId"); - - b.ToTable("logs"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220903071033_LogTime.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220903071033_LogTime.cs deleted file mode 100644 index 74d1e26d..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/20220903071033_LogTime.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Snap.Hutao.Migrations.LogDb -{ - public partial class LogTime : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Time", - table: "logs", - type: "TEXT", - nullable: false, - defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Time", - table: "logs"); - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/LogDbContextModelSnapshot.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/LogDbContextModelSnapshot.cs deleted file mode 100644 index a2955f48..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Migrations/LogDb/LogDbContextModelSnapshot.cs +++ /dev/null @@ -1,53 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Snap.Hutao.Model.Entity.Database; - -#nullable disable - -namespace Snap.Hutao.Migrations.LogDb -{ - [DbContext(typeof(LogDbContext))] - partial class LogDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.8"); - - modelBuilder.Entity("Snap.Hutao.Core.Logging.LogEntry", b => - { - b.Property("InnerId") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Category") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EventId") - .HasColumnType("INTEGER"); - - b.Property("Exception") - .HasColumnType("TEXT"); - - b.Property("LogLevel") - .HasColumnType("INTEGER"); - - b.Property("Message") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Time") - .HasColumnType("TEXT"); - - b.HasKey("InnerId"); - - b.ToTable("logs"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/LaunchGame/LaunchScheme.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/LaunchGame/LaunchScheme.cs index 0c5db92e..9b3b0a34 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/LaunchGame/LaunchScheme.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/LaunchGame/LaunchScheme.cs @@ -15,9 +15,9 @@ public class LaunchScheme /// public static readonly ImmutableList KnownSchemes = new List() { - new LaunchScheme("官方服 | 天空岛", "eYd89JmJ", "18", "1", "1"), - new LaunchScheme("渠道服 | 世界树", "KAtdSsoQ", "17", "14", "0"), - new LaunchScheme("国际服 | 部分支持", "gcStgarh", "10", "1", "0"), + new LaunchScheme("官方服", "eYd89JmJ", "18", "1", "1"), + new LaunchScheme("渠道服", "KAtdSsoQ", "17", "14", "0"), + new LaunchScheme("国际服", "gcStgarh", "10", "1", "0"), }.ToImmutableList(); /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/LogDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/LogDbContext.cs deleted file mode 100644 index 79197c6b..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/LogDbContext.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.EntityFrameworkCore; -using Snap.Hutao.Core.Logging; - -namespace Snap.Hutao.Model.Entity.Database; - -/// -/// 日志数据库上下文 -/// 由于写入日志的行为需要锁定数据库上下文 -/// 所以将日志单独分离出来进行读写 -/// -public class LogDbContext : DbContext -{ - /// - /// 创建一个新的 - /// - /// 选项 - private LogDbContext(DbContextOptions options) - : base(options) - { - } - - /// - /// 日志记录 - /// - public DbSet Logs { get; set; } = default!; - - /// - /// 构造一个临时的日志数据库上下文 - /// - /// 连接字符串 - /// 日志数据库上下文 - public static LogDbContext Create(string sqlConnectionString) - { - return new(new DbContextOptionsBuilder().UseSqlite(sqlConnectionString).Options); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/LogDbContextDesignTimeFactory.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/LogDbContextDesignTimeFactory.cs deleted file mode 100644 index 419b50be..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/LogDbContextDesignTimeFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.EntityFrameworkCore.Design; -using Snap.Hutao.Model.Entity.Database; - -namespace Snap.Hutao.Context.Database; - -/// -/// 此类只用于在生成迁移时提供数据库上下文 -/// -[EditorBrowsable(EditorBrowsableState.Never)] -public class LogDbContextDesignTimeFactory : IDesignTimeDbContextFactory -{ - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public LogDbContext CreateDbContext(string[] args) - { - string logDbName = System.IO.Path.Combine(Core.CoreEnvironment.DataFolder, "Log.db"); - return LogDbContext.Create($"Data Source={logDbName}"); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Package.StoreAssociation.xml b/src/Snap.Hutao/Snap.Hutao/Package.StoreAssociation.xml index 1386cb9a..509d52ab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.StoreAssociation.xml +++ b/src/Snap.Hutao/Snap.Hutao/Package.StoreAssociation.xml @@ -5,6 +5,7 @@ MSA http://www.w3.org/2001/04/xmlenc#sha256 + diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest index 55dcece4..f032975c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest @@ -25,7 +25,8 @@ - + + diff --git a/src/Snap.Hutao/Snap.Hutao/Program.cs b/src/Snap.Hutao/Snap.Hutao/Program.cs index 74121a77..dcbb1de3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Program.cs +++ b/src/Snap.Hutao/Snap.Hutao/Program.cs @@ -5,7 +5,6 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml; using Microsoft.Windows.AppLifecycle; -using Snap.Hutao.Core.Logging; using System.Runtime.InteropServices; using WinRT; @@ -33,7 +32,7 @@ public static partial class Program // In a Desktop app this runs a message pump internally, // and does not return until the application shuts down. Application.Start(InitializeApp); - ServiceScopeExtension.DisposeLast(); + Control.ScopedPage.DisposePreviousScope(); } AppInstance.GetCurrent().UnregisterKey(); @@ -54,11 +53,11 @@ public static partial class Program ServiceProvider services = new ServiceCollection() // Microsoft extension - .AddLogging(builder => builder.AddDebug().AddDatabase()) + .AddLogging(builder => builder.AddDebug()) .AddMemoryCache() // Hutao extensions - .AddJsonSerializerOptions() + .AddJsonOptions() .AddDatebase() .AddInjections() .AddHttpClients() @@ -66,7 +65,7 @@ public static partial class Program // Discrete services .AddSingleton(WeakReferenceMessenger.Default) - .BuildServiceProvider(); + .BuildServiceProvider(true); Ioc.Default.ConfigureServices(services); return services; diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs new file mode 100644 index 00000000..98725880 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs @@ -0,0 +1,648 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace Snap.Hutao.Resource.Localization { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SH { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SH() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Snap.Hutao.Resource.Localization.SH", typeof(SH).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 胡桃 Dev {0} 的本地化字符串。 + /// + internal static string AppDevNameAndVersion { + get { + return ResourceManager.GetString("AppDevNameAndVersion", resourceCulture); + } + } + + /// + /// 查找类似 胡桃 {0} 的本地化字符串。 + /// + internal static string AppNameAndVersion { + get { + return ResourceManager.GetString("AppNameAndVersion", resourceCulture); + } + } + + /// + /// 查找类似 无效的 Uri 的本地化字符串。 + /// + internal static string ControlImageCachedImageInvalidResourceUri { + get { + return ResourceManager.GetString("ControlImageCachedImageInvalidResourceUri", resourceCulture); + } + } + + /// + /// 查找类似 HTTP GET {0} 的本地化字符串。 + /// + internal static string ControlImageCompositionImageHttpRequest { + get { + return ResourceManager.GetString("ControlImageCompositionImageHttpRequest", resourceCulture); + } + } + + /// + /// 查找类似 应用 CompositionImage 的源时发生异常 的本地化字符串。 + /// + internal static string ControlImageCompositionImageSystemException { + get { + return ResourceManager.GetString("ControlImageCompositionImageSystemException", resourceCulture); + } + } + + /// + /// 查找类似 网格 的本地化字符串。 + /// + internal static string ControlPanelPanelSelectorDropdownGridName { + get { + return ResourceManager.GetString("ControlPanelPanelSelectorDropdownGridName", resourceCulture); + } + } + + /// + /// 查找类似 列表 的本地化字符串。 + /// + internal static string ControlPanelPanelSelectorDropdownListName { + get { + return ResourceManager.GetString("ControlPanelPanelSelectorDropdownListName", resourceCulture); + } + } + + /// + /// 查找类似 用户数据已损坏: {0} 的本地化字符串。 + /// + internal static string CoreExceptionServiceUserdataCorruptedMessage { + get { + return ResourceManager.GetString("CoreExceptionServiceUserdataCorruptedMessage", resourceCulture); + } + } + + /// + /// 查找类似 请勿在管理员模式下使用此功能 {0} 的本地化字符串。 + /// + internal static string CoreIOPickerExtensionPickerExceptionInfoBarMessage { + get { + return ResourceManager.GetString("CoreIOPickerExtensionPickerExceptionInfoBarMessage", resourceCulture); + } + } + + /// + /// 查找类似 无法打开文件选择器 的本地化字符串。 + /// + internal static string CoreIOPickerExtensionPickerExceptionInfoBarTitle { + get { + return ResourceManager.GetString("CoreIOPickerExtensionPickerExceptionInfoBarTitle", resourceCulture); + } + } + + /// + /// 查找类似 启动游戏 的本地化字符串。 + /// + internal static string CoreJumpListHelperLaunchGameItemDisplayName { + get { + return ResourceManager.GetString("CoreJumpListHelperLaunchGameItemDisplayName", resourceCulture); + } + } + + /// + /// 查找类似 快捷操作 的本地化字符串。 + /// + internal static string CoreJumpListHelperLaunchGameItemGroupName { + get { + return ResourceManager.GetString("CoreJumpListHelperLaunchGameItemGroupName", resourceCulture); + } + } + + /// + /// 查找类似 胡桃实时便笺刷新任务 | 请勿编辑或删除。 的本地化字符串。 + /// + internal static string CoreScheduleTaskHelperDailyNoteRefreshTaskDescription { + get { + return ResourceManager.GetString("CoreScheduleTaskHelperDailyNoteRefreshTaskDescription", resourceCulture); + } + } + + /// + /// 查找类似 信号量已经被释放,操作取消 的本地化字符串。 + /// + internal static string CoreThreadingSemaphoreSlimDisposed { + get { + return ResourceManager.GetString("CoreThreadingSemaphoreSlimDisposed", resourceCulture); + } + } + + /// + /// 查找类似 未检测到 WebView2 运行时 的本地化字符串。 + /// + internal static string CoreWebView2HelperVersionUndetected { + get { + return ResourceManager.GetString("CoreWebView2HelperVersionUndetected", resourceCulture); + } + } + + /// + /// 查找类似 取消 的本地化字符串。 + /// + internal static string FactoryContentDialogFactoryCancelCloseButtonText { + get { + return ResourceManager.GetString("FactoryContentDialogFactoryCancelCloseButtonText", resourceCulture); + } + } + + /// + /// 查找类似 确认 的本地化字符串。 + /// + internal static string FactoryContentDialogFactoryConfirmPrimaryButtonText { + get { + return ResourceManager.GetString("FactoryContentDialogFactoryConfirmPrimaryButtonText", resourceCulture); + } + } + + /// + /// 查找类似 单个成就存档内发现多个相同的成就 Id 的本地化字符串。 + /// + internal static string ServiceAchievementUserdataCorruptedInnerIdNotUnique { + get { + return ResourceManager.GetString("ServiceAchievementUserdataCorruptedInnerIdNotUnique", resourceCulture); + } + } + + /// + /// 查找类似 开始游戏 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierActionLaunchGameButton { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierActionLaunchGameButton", resourceCulture); + } + } + + /// + /// 查找类似 我知道了 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierActionLaunchGameDismiss { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierActionLaunchGameDismiss", resourceCulture); + } + } + + /// + /// 查找类似 请求异常 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierAttribution { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierAttribution", resourceCulture); + } + } + + /// + /// 查找类似 每日委托 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierDailyTask { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierDailyTask", resourceCulture); + } + } + + /// + /// 查找类似 奖励未领取 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierDailyTaskHint { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierDailyTaskHint", resourceCulture); + } + } + + /// + /// 查找类似 探索派遣 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierExpedition { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierExpedition", resourceCulture); + } + } + + /// + /// 查找类似 已完成 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierExpeditionAdaptiveHint { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierExpeditionAdaptiveHint", resourceCulture); + } + } + + /// + /// 查找类似 探索派遣已完成 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierExpeditionHint { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierExpeditionHint", resourceCulture); + } + } + + /// + /// 查找类似 洞天宝钱 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierHomeCoin { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierHomeCoin", resourceCulture); + } + } + + /// + /// 查找类似 当前洞天宝钱:{0} 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierHomeCoinCurrent { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierHomeCoinCurrent", resourceCulture); + } + } + + /// + /// 查找类似 多个提醒项达到设定值 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierMultiValueReached { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierMultiValueReached", resourceCulture); + } + } + + /// + /// 查找类似 原粹树脂 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierResin { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierResin", resourceCulture); + } + } + + /// + /// 查找类似 当前原粹树脂:{0} 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierResinCurrent { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierResinCurrent", resourceCulture); + } + } + + /// + /// 查找类似 实时便笺提醒 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierTitle { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierTitle", resourceCulture); + } + } + + /// + /// 查找类似 参量质变仪 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierTransformer { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierTransformer", resourceCulture); + } + } + + /// + /// 查找类似 准备完成 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierTransformerAdaptiveHint { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierTransformerAdaptiveHint", resourceCulture); + } + } + + /// + /// 查找类似 参量质变仪已准备完成 的本地化字符串。 + /// + internal static string ServiceDailyNoteNotifierTransformerHint { + get { + return ResourceManager.GetString("ServiceDailyNoteNotifierTransformerHint", resourceCulture); + } + } + + /// + /// 查找类似 无法获取祈愿记录: {0} 的本地化字符串。 + /// + internal static string ServiceGachaLogArchiveCollectionUserdataCorruptedMessage { + get { + return ResourceManager.GetString("ServiceGachaLogArchiveCollectionUserdataCorruptedMessage", resourceCulture); + } + } + + /// + /// 查找类似 无法获取祈愿记录 End Id 的本地化字符串。 + /// + internal static string ServiceGachaLogEndIdUserdataCorruptedMessage { + get { + return ResourceManager.GetString("ServiceGachaLogEndIdUserdataCorruptedMessage", resourceCulture); + } + } + + /// + /// 查找类似 角色活动 的本地化字符串。 + /// + internal static string ServiceGachaLogFactoryAvatarWishName { + get { + return ResourceManager.GetString("ServiceGachaLogFactoryAvatarWishName", resourceCulture); + } + } + + /// + /// 查找类似 奔行世间 的本地化字符串。 + /// + internal static string ServiceGachaLogFactoryPermanentWishName { + get { + return ResourceManager.GetString("ServiceGachaLogFactoryPermanentWishName", resourceCulture); + } + } + + /// + /// 查找类似 神铸赋形 的本地化字符串。 + /// + internal static string ServiceGachaLogFactoryWeaponWishName { + get { + return ResourceManager.GetString("ServiceGachaLogFactoryWeaponWishName", resourceCulture); + } + } + + /// + /// 查找类似 请求验证密钥失败 的本地化字符串。 + /// + internal static string ServiceGachaLogUrlProviderAuthkeyRequestFailed { + get { + return ResourceManager.GetString("ServiceGachaLogUrlProviderAuthkeyRequestFailed", resourceCulture); + } + } + + /// + /// 查找类似 未正确提供原神路径,或当前设置的路径不正确 的本地化字符串。 + /// + internal static string ServiceGachaLogUrlProviderCachePathInvalid { + get { + return ResourceManager.GetString("ServiceGachaLogUrlProviderCachePathInvalid", resourceCulture); + } + } + + /// + /// 查找类似 找不到原神内置浏览器缓存路径:\n{0} 的本地化字符串。 + /// + internal static string ServiceGachaLogUrlProviderCachePathNotFound { + get { + return ResourceManager.GetString("ServiceGachaLogUrlProviderCachePathNotFound", resourceCulture); + } + } + + /// + /// 查找类似 未找到可用的 Url 的本地化字符串。 + /// + internal static string ServiceGachaLogUrlProviderCacheUrlNotFound { + get { + return ResourceManager.GetString("ServiceGachaLogUrlProviderCacheUrlNotFound", resourceCulture); + } + } + + /// + /// 查找类似 提供的Url无效 的本地化字符串。 + /// + internal static string ServiceGachaLogUrlProviderManualInputInvalid { + get { + return ResourceManager.GetString("ServiceGachaLogUrlProviderManualInputInvalid", resourceCulture); + } + } + + /// + /// 查找类似 存在多个匹配账号,请删除重复的账号 的本地化字符串。 + /// + internal static string ServiceGameDetectGameAccountMultiMatched { + get { + return ResourceManager.GetString("ServiceGameDetectGameAccountMultiMatched", resourceCulture); + } + } + + /// + /// 查找类似 查询游戏资源信息 的本地化字符串。 + /// + internal static string ServiceGameEnsureGameResourceQueryResourceInformation { + get { + return ResourceManager.GetString("ServiceGameEnsureGameResourceQueryResourceInformation", resourceCulture); + } + } + + /// + /// 查找类似 游戏文件操作失败: {0} 的本地化字符串。 + /// + internal static string ServiceGameFileOperationExceptionMessage { + get { + return ResourceManager.GetString("ServiceGameFileOperationExceptionMessage", resourceCulture); + } + } + + /// + /// 查找类似 选择游戏本体 的本地化字符串。 + /// + internal static string ServiceGameLocatorFileOpenPickerCommitText { + get { + return ResourceManager.GetString("ServiceGameLocatorFileOpenPickerCommitText", resourceCulture); + } + } + + /// + /// 查找类似 找不到 Unity 日志文件 的本地化字符串。 + /// + internal static string ServiceGameLocatorUnityLogFileNotFound { + get { + return ResourceManager.GetString("ServiceGameLocatorUnityLogFileNotFound", resourceCulture); + } + } + + /// + /// 查找类似 在 Unity 日志文件中找不到游戏路径 的本地化字符串。 + /// + internal static string ServiceGameLocatorUnityLogGamePathNotFound { + get { + return ResourceManager.GetString("ServiceGameLocatorUnityLogGamePathNotFound", resourceCulture); + } + } + + /// + /// 查找类似 获取 Package Version 的本地化字符串。 + /// + internal static string ServiceGamePackageRequestPackageVerion { + get { + return ResourceManager.GetString("ServiceGamePackageRequestPackageVerion", resourceCulture); + } + } + + /// + /// 查找类似 获取 Package Version 失败 的本地化字符串。 + /// + internal static string ServiceGamePackageRequestPackageVerionFailed { + get { + return ResourceManager.GetString("ServiceGamePackageRequestPackageVerionFailed", resourceCulture); + } + } + + /// + /// 查找类似 无法找到游戏路径,请前往设置修改 的本地化字符串。 + /// + internal static string ServiceGamePathLocateFailed { + get { + return ResourceManager.GetString("ServiceGamePathLocateFailed", resourceCulture); + } + } + + /// + /// 查找类似 找不到游戏配置文件 {0} 的本地化字符串。 + /// + internal static string ServiceGameSetMultiChannelConfigFileNotFound { + get { + return ResourceManager.GetString("ServiceGameSetMultiChannelConfigFileNotFound", resourceCulture); + } + } + + /// + /// 查找类似 无法读取或保存配置文件,请以管理员模式重试 的本地化字符串。 + /// + internal static string ServiceGameSetMultiChannelUnauthorizedAccess { + get { + return ResourceManager.GetString("ServiceGameSetMultiChannelUnauthorizedAccess", resourceCulture); + } + } + + /// + /// 查找类似 元数据服务尚未初始化,或初始化失败 的本地化字符串。 + /// + internal static string ServiceMetadataNotInitialized { + get { + return ResourceManager.GetString("ServiceMetadataNotInitialized", resourceCulture); + } + } + + /// + /// 查找类似 元数据校验文件解析失败 的本地化字符串。 + /// + internal static string ServiceMetadataParseFailed { + get { + return ResourceManager.GetString("ServiceMetadataParseFailed", resourceCulture); + } + } + + /// + /// 查找类似 元数据校验文件下载失败 的本地化字符串。 + /// + internal static string ServiceMetadataRequestFailed { + get { + return ResourceManager.GetString("ServiceMetadataRequestFailed", resourceCulture); + } + } + + /// + /// 查找类似 尚未选择任何用户以及角色 的本地化字符串。 + /// + internal static string ServiceUserAndRoleUnselected { + get { + return ResourceManager.GetString("ServiceUserAndRoleUnselected", resourceCulture); + } + } + + /// + /// 查找类似 多个用户记录为选中状态 的本地化字符串。 + /// + internal static string ServiceUserCurrentMultiMatched { + get { + return ResourceManager.GetString("ServiceUserCurrentMultiMatched", resourceCulture); + } + } + + /// + /// 查找类似 用户 {0} 状态保存失败 的本地化字符串。 + /// + internal static string ServiceUserCurrentUpdateAndSaveFailed { + get { + return ResourceManager.GetString("ServiceUserCurrentUpdateAndSaveFailed", resourceCulture); + } + } + + /// + /// 查找类似 输入的 Cookie 必须包含 Mid 的本地化字符串。 + /// + internal static string ServiceUserProcessCookieNoMid { + get { + return ResourceManager.GetString("ServiceUserProcessCookieNoMid", resourceCulture); + } + } + + /// + /// 查找类似 输入的 Cookie 必须包含 Stoken 的本地化字符串。 + /// + internal static string ServiceUserProcessCookieNoStoken { + get { + return ResourceManager.GetString("ServiceUserProcessCookieNoStoken", resourceCulture); + } + } + + /// + /// 查找类似 输入的 Cookie 无法获取用户信息 的本地化字符串。 + /// + internal static string ServiceUserProcessCookieRequestUserInfoFailed { + get { + return ResourceManager.GetString("ServiceUserProcessCookieRequestUserInfoFailed", resourceCulture); + } + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx new file mode 100644 index 00000000..7f5046ac --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 胡桃 Dev {0} + + + 胡桃 {0} + + + 无效的 Uri + + + HTTP GET {0} + + + 应用 CompositionImage 的源时发生异常 + + + 网格 + + + 列表 + + + 用户数据已损坏: {0} + + + 请勿在管理员模式下使用此功能 {0} + + + 无法打开文件选择器 + + + 启动游戏 + + + 快捷操作 + + + 胡桃实时便笺刷新任务 | 请勿编辑或删除。 + + + 信号量已经被释放,操作取消 + + + 未检测到 WebView2 运行时 + + + 取消 + + + 确认 + + + 单个成就存档内发现多个相同的成就 Id + + + 开始游戏 + + + 我知道了 + + + 请求异常 + + + 每日委托 + + + 奖励未领取 + + + 探索派遣 + + + 已完成 + + + 探索派遣已完成 + + + 洞天宝钱 + + + 当前洞天宝钱:{0} + + + 多个提醒项达到设定值 + + + 原粹树脂 + + + 当前原粹树脂:{0} + + + 实时便笺提醒 + + + 参量质变仪 + + + 准备完成 + + + 参量质变仪已准备完成 + + + 无法获取祈愿记录: {0} + + + 无法获取祈愿记录 End Id + + + 角色活动 + + + 奔行世间 + + + 神铸赋形 + + + 请求验证密钥失败 + + + 未正确提供原神路径,或当前设置的路径不正确 + + + 找不到原神内置浏览器缓存路径:\n{0} + + + 未找到可用的 Url + + + 提供的Url无效 + + + 存在多个匹配账号,请删除重复的账号 + + + 查询游戏资源信息 + + + 游戏文件操作失败: {0} + + + 选择游戏本体 + + + 找不到 Unity 日志文件 + + + 在 Unity 日志文件中找不到游戏路径 + + + 获取 Package Version + + + 获取 Package Version 失败 + + + 无法找到游戏路径,请前往设置修改 + + + 找不到游戏配置文件 {0} + + + 无法读取或保存配置文件,请以管理员模式重试 + + + 元数据服务尚未初始化,或初始化失败 + + + 元数据校验文件解析失败 + + + 元数据校验文件下载失败 + + + 尚未选择任何用户以及角色 + + + 多个用户记录为选中状态 + + + 用户 {0} 状态保存失败 + + + 输入的 Cookie 必须包含 Mid + + + 输入的 Cookie 必须包含 Stoken + + + 输入的 Cookie 无法获取用户信息 + + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs index b4119679..95b359bd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs @@ -114,14 +114,14 @@ internal class AchievementService : IAchievementService List results = new(); foreach (MetadataAchievement meta in metadata) { - EntityAchievement? entity; + EntityAchievement? entity = null; try { entity = entities.SingleOrDefault(e => e.Id == meta.Id); } catch (InvalidOperationException ex) { - throw new UserdataCorruptedException("单个成就存档内发现多个相同的成就 Id", ex); + ThrowHelper.UserdataCorrupted(SH.ServiceAchievementUserdataCorruptedInnerIdNotUnique, ex); } entity ??= EntityAchievement.Create(archiveId, meta.Id); @@ -154,7 +154,6 @@ internal class AchievementService : IAchievementService /// public async Task ImportFromUIAFAsync(EntityArchive archive, List list, ImportStrategy strategy) { - // return Task.Run(() => ImportFromUIAF(archive, list, strategy)); await ThreadHelper.SwitchToBackgroundAsync(); Guid archiveId = archive.InnerId; @@ -190,7 +189,7 @@ internal class AchievementService : IAchievementService public void SaveAchievements(EntityArchive archive, IList achievements) { string name = archive.Name; - logger.LogInformation(EventIds.Achievement, "Begin saving achievements for [{name}]", name); + logger.LogInformation("Begin saving achievements for [{name}]", name); ValueStopwatch stopwatch = ValueStopwatch.StartNew(); IEnumerable newData = achievements @@ -200,8 +199,8 @@ internal class AchievementService : IAchievementService ImportResult result = achievementDbOperation.Overwrite(archive.InnerId, newData); double time = stopwatch.GetElapsedTime().TotalMilliseconds; - logger.LogInformation(EventIds.Achievement, "{add} added, {update} updated, {remove} removed", result.Add, result.Update, result.Remove); - logger.LogInformation(EventIds.Achievement, "Save achievements for [{name}] completed in {time}ms", name, time); + logger.LogInformation("{add} added, {update} updated, {remove} removed", result.Add, result.Update, result.Remove); + logger.LogInformation("Save achievements for [{name}] completed in {time}ms", name, time); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs index e739fa78..7195f3d2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AnnouncementService.cs @@ -57,7 +57,7 @@ internal partial class AnnouncementService : IAnnouncementService Dictionary contentMap = contents .ToDictionary(id => id.AnnId, content => content.Content); - // 将活动公告置于上方 + // 将活动公告置于前方 wrapper.List.Reverse(); // 将公告内容联入公告列表 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryImplementation.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryImplementation.cs index 20428588..b137d83a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryImplementation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryImplementation.cs @@ -35,11 +35,10 @@ internal class SummaryFactoryImplementation return new() { Player = SummaryHelper.CreatePlayer(playerInfo), - Avatars = avatarInfos.Where(a => !AvatarIds.IsPlayer(a.AvatarId)).Select(a => - { - SummaryAvatarFactory summaryAvatarFactory = new(metadataContext, a); - return summaryAvatarFactory.CreateAvatar(); - }).ToList(), + Avatars = avatarInfos + .Where(a => !AvatarIds.IsPlayer(a.AvatarId)) + .Select(a => new SummaryAvatarFactory(metadataContext, a).CreateAvatar()) + .ToList(), }; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs index ce2c4919..a62c991e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs @@ -54,10 +54,10 @@ internal class DailyNoteNotifier if (!entry.ResinNotifySuppressed) { notifyInfos.Add(new( - "原粹树脂", + SH.ServiceDailyNoteNotifierResin, "ms-appx:///Resource/Icon/UI_ItemIcon_210_256.png", $"{entry.DailyNote.CurrentResin}", - $"当前原粹树脂:{entry.DailyNote.CurrentResin}")); + string.Format(SH.ServiceDailyNoteNotifierResinCurrent, entry.DailyNote.CurrentResin))); entry.ResinNotifySuppressed = true; } } @@ -71,10 +71,10 @@ internal class DailyNoteNotifier if (!entry.HomeCoinNotifySuppressed) { notifyInfos.Add(new( - "洞天宝钱", + SH.ServiceDailyNoteNotifierHomeCoin, "ms-appx:///Resource/Icon/UI_ItemIcon_204.png", $"{entry.DailyNote.CurrentHomeCoin}", - $"当前洞天宝钱:{entry.DailyNote.CurrentHomeCoin}")); + string.Format(SH.ServiceDailyNoteNotifierHomeCoinCurrent, entry.DailyNote.CurrentHomeCoin))); entry.HomeCoinNotifySuppressed = true; } } @@ -88,9 +88,9 @@ internal class DailyNoteNotifier if (!entry.DailyTaskNotifySuppressed) { notifyInfos.Add(new( - "每日委托", + SH.ServiceDailyNoteNotifierDailyTask, "ms-appx:///Resource/Icon/UI_MarkQuest_Events_Proce.png", - $"奖励待领取", + SH.ServiceDailyNoteNotifierDailyTaskHint, entry.DailyNote.ExtraTaskRewardDescription)); entry.DailyTaskNotifySuppressed = true; } @@ -105,10 +105,10 @@ internal class DailyNoteNotifier if (!entry.TransformerNotifySuppressed) { notifyInfos.Add(new( - "参量质变仪", + SH.ServiceDailyNoteNotifierTransformer, "ms-appx:///Resource/Icon/UI_ItemIcon_220021.png", - $"准备完成", - "参量质变仪已准备完成")); + SH.ServiceDailyNoteNotifierTransformerAdaptiveHint, + SH.ServiceDailyNoteNotifierTransformerHint)); entry.TransformerNotifySuppressed = true; } } @@ -122,10 +122,10 @@ internal class DailyNoteNotifier if (!entry.ExpeditionNotifySuppressed) { notifyInfos.Add(new( - "探索派遣", - AvatarIconConverter.IconNameToUri("UI_AvatarIcon_Side_None.png").ToString(), - $"已完成", - "探索派遣已完成")); + SH.ServiceDailyNoteNotifierExpedition, + Web.HutaoEndpoints.UIAvatarIconSideNone.ToString(), // TODO: embed this + SH.ServiceDailyNoteNotifierExpeditionAdaptiveHint, + SH.ServiceDailyNoteNotifierExpeditionHint)); entry.ExpeditionNotifySuppressed = true; } } @@ -150,7 +150,7 @@ internal class DailyNoteNotifier .GetActionTicketByStokenAsync("game_role", entry.User) .ConfigureAwait(false); - string? attribution = "请求异常"; + string? attribution = SH.ServiceDailyNoteNotifierAttribution; if (actionTicketResponse.IsOk()) { Response> rolesResponse = await scope.ServiceProvider @@ -166,10 +166,13 @@ internal class DailyNoteNotifier } ToastContentBuilder builder = new ToastContentBuilder() - .AddHeader("DAILYNOTE", "实时便笺提醒", "DAILYNOTE") + .AddHeader("DAILYNOTE", SH.ServiceDailyNoteNotifierTitle, "DAILYNOTE") .AddAttributionText(attribution) - .AddButton(new ToastButton().SetContent("开始游戏").AddArgument("Action", "LaunchGame").AddArgument("Uid", entry.Uid)) - .AddButton(new ToastButtonDismiss("我知道了")); + .AddButton(new ToastButton() + .SetContent(SH.ServiceDailyNoteNotifierActionLaunchGameButton) + .AddArgument("Action", Core.LifeCycle.Activation.LaunchGame) + .AddArgument("Uid", entry.Uid)) + .AddButton(new ToastButtonDismiss(SH.ServiceDailyNoteNotifierActionLaunchGameDismiss)); if (appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString).GetBoolean()) { @@ -178,7 +181,7 @@ internal class DailyNoteNotifier if (notifyInfos.Count > 2) { - builder.AddText("多个提醒项达到设定值"); + builder.AddText(SH.ServiceDailyNoteNotifierMultiValueReached); // Desktop and Mobile started supporting adaptive toasts in API contract 3 (Anniversary Update) if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3)) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs index 4b08bb1d..71d60414 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs @@ -65,9 +65,9 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory Dictionary weaponMap, bool isEmptyHistoryWishVisible) { - TypedWishSummaryBuilder permanentWishBuilder = new("奔行世间", TypedWishSummaryBuilder.IsPermanentWish, 90, 10); - TypedWishSummaryBuilder avatarWishBuilder = new("角色活动", TypedWishSummaryBuilder.IsAvatarEventWish, 90, 10); - TypedWishSummaryBuilder weaponWishBuilder = new("神铸赋形", TypedWishSummaryBuilder.IsWeaponEventWish, 80, 10); + TypedWishSummaryBuilder permanentWishBuilder = new(SH.ServiceGachaLogFactoryPermanentWishName, TypedWishSummaryBuilder.IsPermanentWish, 90, 10); + TypedWishSummaryBuilder avatarWishBuilder = new(SH.ServiceGachaLogFactoryAvatarWishName, TypedWishSummaryBuilder.IsAvatarEventWish, 90, 10); + TypedWishSummaryBuilder weaponWishBuilder = new(SH.ServiceGachaLogFactoryWeaponWishName, TypedWishSummaryBuilder.IsWeaponEventWish, 80, 10); Dictionary orangeAvatarCounter = new(); Dictionary purpleAvatarCounter = new(); @@ -136,6 +136,7 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory else { // ItemId place not correct. + // TODO: check items id when importing Must.NeverHappen(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs index acd09fe9..951508d4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs @@ -6,6 +6,7 @@ using Microsoft.Data.Sqlite; 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.Binding.Gacha; @@ -119,12 +120,14 @@ internal class GachaLogService : IGachaLogService await ThreadHelper.SwitchToMainThreadAsync(); try { - return archiveCollection ??= appDbContext.GachaArchives.AsNoTracking().ToObservableCollection(); + archiveCollection ??= appDbContext.GachaArchives.AsNoTracking().ToObservableCollection(); } catch (SqliteException ex) { - throw new Core.ExceptionService.UserdataCorruptedException($"无法获取祈愿记录: {ex.Message}", ex); + ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceGachaLogArchiveCollectionUserdataCorruptedMessage, ex.Message), ex); } + + return archiveCollection; } /// @@ -349,7 +352,7 @@ internal class GachaLogService : IGachaLogService } catch (SqliteException ex) { - throw new Core.ExceptionService.UserdataCorruptedException("无法获取祈愿记录 End Id", ex); + ThrowHelper.UserdataCorrupted(SH.ServiceGachaLogEndIdUserdataCorruptedMessage, ex); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs index 3be7213f..850ba224 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs @@ -29,7 +29,7 @@ internal class GachaLogUrlManualInputProvider : IGachaLogUrlProvider } else { - return new(false, "提供的Url无效"); + return new(false, SH.ServiceGachaLogUrlProviderManualInputInvalid); } } else diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs index 68f481c1..c6c28269 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs @@ -1,10 +1,11 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Service.User; -using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; +using Snap.Hutao.Web.Response; namespace Snap.Hutao.Service.GachaLog; @@ -34,32 +35,23 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider /// public async Task> GetQueryAsync() { - Model.Binding.User.User? user = userService.Current; - if (user != null && user.SelectedUserGameRole != null) + if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) { - if (user.Stoken != null) - { - PlayerUid uid = (PlayerUid)user.SelectedUserGameRole; - GenAuthKeyData data = GenAuthKeyData.CreateForWebViewGacha(uid); + GenAuthKeyData data = GenAuthKeyData.CreateForWebViewGacha(userAndUid.Uid); + Response authkeyResponse = await bindingClient2.GenerateAuthenticationKeyAsync(userAndUid.User, data).ConfigureAwait(false); - Web.Response.Response authkeyResponse = await bindingClient2.GenerateAuthenticationKeyAsync(user.Entity, data).ConfigureAwait(false); - if (authkeyResponse.IsOk()) - { - return new(true, GachaLogConfigration.AsQuery(data, authkeyResponse.Data)); - } - else - { - return new(false, "请求验证密钥失败"); - } + if (authkeyResponse.IsOk()) + { + return new(true, GachaLogConfigration.AsQuery(data, authkeyResponse.Data)); } else { - return new(false, "当前用户的 Cookie 不包含 Stoken"); + return new(false, SH.ServiceGachaLogUrlProviderAuthkeyRequestFailed); } } else { - return new(false, "尚未选择要刷新的用户以及角色"); + return new(false, SH.ServiceUserAndRoleUnselected); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs index 1ea05d84..11239381 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs @@ -56,7 +56,7 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider { if (tempFile == null) { - return new(false, $"找不到原神内置浏览器缓存路径:\n{cacheFile}"); + return new(false, string.Format(SH.ServiceGachaLogUrlProviderCachePathNotFound, cacheFile)); } using (FileStream fileStream = new(tempFile.Path, FileMode.Open, FileAccess.Read, FileShare.Read)) @@ -65,14 +65,14 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider { await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); string? result = Match(memoryStream, cacheFile.Contains(GameConstants.GenshinImpactData)); - return new(!string.IsNullOrEmpty(result), result ?? "未找到可用的 Url"); + return new(!string.IsNullOrEmpty(result), result ?? SH.ServiceGachaLogUrlProviderCacheUrlNotFound); } } } } else { - return new(false, "未正确提供原神路径,或当前设置的路径不正确"); + return new(false, SH.ServiceGachaLogUrlProviderCachePathInvalid); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs index b0f152f2..5b04800b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileOperationException.cs @@ -14,7 +14,7 @@ internal class GameFileOperationException : Exception /// 消息 /// 内部错误 public GameFileOperationException(string message, Exception innerException) - : base($"游戏文件操作失败: {message}", innerException) + : base(string.Format(SH.ServiceGameFileOperationExceptionMessage, message), innerException) { } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs index 2e503f9b..1d294d1d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Snap.Hutao.Core; using Snap.Hutao.Core.Database; +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.IO.Ini; using Snap.Hutao.Extension; using Snap.Hutao.Model.Binding.LaunchGame; @@ -92,7 +93,7 @@ internal class GameService : IGameService } else { - return new(false, "请启动游戏后再次尝试"); + return new(false, SH.ServiceGamePathLocateFailed); } } @@ -181,15 +182,15 @@ internal class GameService : IGameService } catch (FileNotFoundException ex) { - throw new GameFileOperationException($"找不到游戏配置文件 {configPath}", ex); + throw new GameFileOperationException(string.Format(SH.ServiceGameSetMultiChannelConfigFileNotFound, configPath), ex); } catch (DirectoryNotFoundException ex) { - throw new GameFileOperationException($"找不到游戏配置文件 {configPath}", ex); + throw new GameFileOperationException(string.Format(SH.ServiceGameSetMultiChannelConfigFileNotFound, configPath), ex); } catch (UnauthorizedAccessException ex) { - throw new GameFileOperationException($"无法读取或保存配置文件,请以管理员模式重试。", ex); + throw new GameFileOperationException(SH.ServiceGameSetMultiChannelUnauthorizedAccess, ex); } bool changed = false; @@ -236,7 +237,7 @@ internal class GameService : IGameService string gameFolder = Path.GetDirectoryName(gamePath)!; string gameFileName = Path.GetFileName(gamePath); - progress.Report(new("查询游戏资源信息")); + progress.Report(new(SH.ServiceGameEnsureGameResourceQueryResourceInformation)); Response response = await Ioc.Default .GetRequiredService() .GetResourceAsync(launchScheme) @@ -321,6 +322,7 @@ internal class GameService : IGameService } // https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html + // TODO: impl monitor option. string commandLine = new CommandLineBuilder() .AppendIf("-popupwindow", configuration.IsBorderless) .Append("-screen-fullscreen", configuration.IsFullScreen ? 1 : 0) @@ -343,31 +345,24 @@ internal class GameService : IGameService using (await gameSemaphore.EnterAsync().ConfigureAwait(false)) { - try + if (configuration.UnlockFPS) { - if (configuration.UnlockFPS) - { - IGameFpsUnlocker unlocker = new GameFpsUnlocker(game, configuration.TargetFPS); + IGameFpsUnlocker unlocker = new GameFpsUnlocker(game, configuration.TargetFPS); - TimeSpan findModuleDelay = TimeSpan.FromMilliseconds(100); - TimeSpan findModuleLimit = TimeSpan.FromMilliseconds(10000); - TimeSpan adjustFpsDelay = TimeSpan.FromMilliseconds(2000); - if (game.Start()) - { - await unlocker.UnlockAsync(findModuleDelay, findModuleLimit, adjustFpsDelay).ConfigureAwait(false); - } - } - else + TimeSpan findModuleDelay = TimeSpan.FromMilliseconds(100); + TimeSpan findModuleLimit = TimeSpan.FromMilliseconds(10000); + TimeSpan adjustFpsDelay = TimeSpan.FromMilliseconds(2000); + if (game.Start()) { - if (game.Start()) - { - await game.WaitForExitAsync().ConfigureAwait(false); - } + await unlocker.UnlockAsync(findModuleDelay, findModuleLimit, adjustFpsDelay).ConfigureAwait(false); } } - catch (Win32Exception) + else { - // 通常是用户取消了UAC + if (game.Start()) + { + await game.WaitForExitAsync().ConfigureAwait(false); + } } } } @@ -380,14 +375,14 @@ internal class GameService : IGameService string? registrySdk = RegistryInterop.Get(); if (!string.IsNullOrEmpty(registrySdk)) { - GameAccount? account; + GameAccount? account = null; try { account = gameAccounts.SingleOrDefault(a => a.MihoyoSDK == registrySdk); } catch (InvalidOperationException ex) { - throw new Core.ExceptionService.UserdataCorruptedException("已存在多个匹配账号,请先删除重复的账号", ex); + ThrowHelper.UserdataCorrupted(SH.ServiceGameDetectGameAccountMultiMatched, ex); } if (account == null) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs index 5bd33c0e..f73659f8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/ManualGameLocator.cs @@ -36,7 +36,11 @@ internal class ManualGameLocator : IGameLocator private async Task> LocateInternalAsync(List fileNames) { - FileOpenPicker picker = pickerFactory.GetFileOpenPicker(PickerLocationId.Desktop, "选择游戏本体", ".exe"); + FileOpenPicker picker = pickerFactory.GetFileOpenPicker( + PickerLocationId.Desktop, + SH.ServiceGameLocatorFileOpenPickerCommitText, + ".exe"); + (bool isPickerOk, FilePath file) = await picker.TryPickSingleFileAsync().ConfigureAwait(false); if (isPickerOk) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs index 164d754b..3a3a8293 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs @@ -20,6 +20,7 @@ internal partial class UnityLogGameLocator : IGameLocator public async Task> LocateGamePathAsync() { await ThreadHelper.SwitchToBackgroundAsync(); + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); string logFilePathChinese = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\原神\output_log.txt"); string logFilePathOvsesea = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\Genshin Impact\output_log.txt"); @@ -36,7 +37,7 @@ internal partial class UnityLogGameLocator : IGameLocator Match matchResult = WarmupFileLine().Match(content); if (!matchResult.Success) { - return new(false, $"在 Unity 日志文件中找不到游戏路径"); + return new(false, SH.ServiceGameLocatorUnityLogGamePathNotFound); } string entryName = matchResult.Groups[0].Value.Replace("_Data", ".exe"); @@ -45,7 +46,7 @@ internal partial class UnityLogGameLocator : IGameLocator } else { - return new(false, $"找不到 Unity 日志文件"); + return new(false, SH.ServiceGameLocatorUnityLogFileNotFound); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs index eeb6c25e..1c03c6ea 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.IO; using Snap.Hutao.Model.Binding.LaunchGame; using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher; @@ -49,8 +50,8 @@ internal class PackageConverter Uri pkgVersionUri = new($"{scatteredFilesUrl}/pkg_version"); ConvertDirection direction = targetScheme.IsOversea ? ConvertDirection.ChineseToOversea : ConvertDirection.OverseaToChinese; - progress.Report(new("获取 Package Version")); - Dictionary remoteItems; + progress.Report(new(SH.ServiceGamePackageRequestPackageVerion)); + Dictionary remoteItems = default!; try { using (Stream remoteSteam = await httpClient.GetStreamAsync(pkgVersionUri).ConfigureAwait(false)) @@ -60,7 +61,7 @@ internal class PackageConverter } catch (IOException ex) { - throw new PackageConvertException("下载 Package Version 失败", ex); + ThrowHelper.PackageConvert(SH.ServiceGamePackageRequestPackageVerionFailed, ex); } Dictionary localItems; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs index 00a70bee..9eb84d9e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs @@ -33,7 +33,7 @@ internal class GameFpsUnlocker : IGameFpsUnlocker /// 目标fps public GameFpsUnlocker(Process gameProcess, int targetFPS) { - Must.Range(targetFPS >= 30 && targetFPS <= 2000, "目标FPS超过允许值"); + Must.Range(targetFPS >= 30 && targetFPS <= 2000, "Target FPS threshold exceeded"); TargetFps = targetFPS; this.gameProcess = gameProcess; @@ -45,12 +45,12 @@ internal class GameFpsUnlocker : IGameFpsUnlocker /// public async Task UnlockAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit, TimeSpan adjustFpsDelay) { - Verify.Operation(isValid, "此解锁器已经失效"); + Verify.Operation(isValid, "This Unlocker is invalid"); MODULEENTRY32 unityPlayer = await FindModuleAsync(findModuleDelay, findModuleLimit).ConfigureAwait(false); // Read UnityPlayer.dll - TryReadModuleMemoryFindFpsAddress(unityPlayer); + UnsafeTryReadModuleMemoryFindFpsAddress(unityPlayer); // When player switch between scenes, we have to re adjust the fps // So we keep a loop here @@ -73,7 +73,7 @@ internal class GameFpsUnlocker : IGameFpsUnlocker return WriteProcessMemory(process.SafeHandle, (void*)baseAddress, lpBuffer, sizeof(int), null); } - private static unsafe MODULEENTRY32 FindModule(int processId, string moduleName) + private static unsafe MODULEENTRY32 UnsafeFindModule(int processId, string moduleName) { HANDLE snapshot = CreateToolhelp32Snapshot(CREATE_TOOLHELP_SNAPSHOT_FLAGS.TH32CS_SNAPMODULE, (uint)processId); try @@ -109,7 +109,7 @@ internal class GameFpsUnlocker : IGameFpsUnlocker while (true) { - MODULEENTRY32 module = FindModule(gameProcess.Id, "UnityPlayer.dll"); + MODULEENTRY32 module = UnsafeFindModule(gameProcess.Id, "UnityPlayer.dll"); if (!StructMarshal.IsDefault(module)) { return module; @@ -145,7 +145,7 @@ internal class GameFpsUnlocker : IGameFpsUnlocker } } - private unsafe void TryReadModuleMemoryFindFpsAddress(MODULEENTRY32 unityPlayer) + private unsafe void UnsafeTryReadModuleMemoryFindFpsAddress(MODULEENTRY32 unityPlayer) { bool readOk = UnsafeReadModuleMemory(gameProcess, unityPlayer, out Span image); Verify.Operation(readOk, "读取内存失败"); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlockerException.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlockerException.cs deleted file mode 100644 index 1978a284..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlockerException.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -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; - -namespace Snap.Hutao.Service.Game.Unlocker; - -/// -/// 游戏帧率解锁器异常 -/// -internal class GameFpsUnlockerException : Exception -{ - /// - /// 构造一个新的用户数据损坏异常 - /// - /// 消息 - /// 内部错误 - public GameFpsUnlockerException(Exception innerException) - : base($"解锁帧率失败: {innerException.Message}", innerException) - { - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs index abd90fc1..027bee4d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs @@ -94,13 +94,13 @@ internal partial class MetadataService : IMetadataService, IMetadataServiceIniti if (metaMd5Map is null) { - infoBarService.Error("元数据校验文件解析失败"); + infoBarService.Error(SH.ServiceMetadataParseFailed); return false; } } catch (HttpRequestException ex) { - infoBarService.Error(ex, "元数据校验文件下载失败"); + infoBarService.Error(ex, SH.ServiceMetadataRequestFailed); return false; } @@ -140,7 +140,7 @@ internal partial class MetadataService : IMetadataService, IMetadataServiceIniti if (!skip) { - logger.LogInformation(EventIds.MetadataFileMD5Check, "MD5 of {file} not matched, begin downloading", fileFullName); + logger.LogInformation("MD5 of {file} not matched, begin downloading", fileFullName); await DownloadMetadataAsync(fileFullName, token).ConfigureAwait(false); } @@ -172,7 +172,7 @@ internal partial class MetadataService : IMetadataService, IMetadataServiceIniti private async ValueTask FromCacheOrFileAsync(string fileName, CancellationToken token) where T : class { - Verify.Operation(isInitialized, "元数据服务尚未初始化,或初始化失败"); + Verify.Operation(isInitialized, SH.ServiceMetadataNotInitialized); string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}"; if (memoryCache.TryGetValue(cacheKey, out object? value)) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationService.cs index d61470cb..4db78e63 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationService.cs @@ -48,6 +48,7 @@ public interface INavigationService /// /// 导航到指定类型的页面 + /// 若已经处于当前页面不会向页面发送消息 /// /// 指定的页面类型 /// 要传递的数据 @@ -58,6 +59,7 @@ public interface INavigationService /// /// 异步的导航到指定类型的页面 + /// 若已经处于当前页面则会向页面发送消息 /// /// 指定的页面类型 /// 要传递的数据 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs index 52a2cf63..28abd4d6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs @@ -102,7 +102,7 @@ internal class NavigationService : INavigationService if (currentType == pageType) { - logger.LogInformation(EventIds.NavigationHistory, "Navigate to {pageType} : succeed, already in", pageType); + logger.LogInformation("Navigate to {pageType} : succeed, already in", pageType); return NavigationResult.AlreadyNavigatedTo; } @@ -112,11 +112,11 @@ internal class NavigationService : INavigationService try { navigated = Frame?.Navigate(pageType, data) ?? false; - logger.LogInformation(EventIds.NavigationHistory, "Navigate to {pageType} : {result}", pageType, navigated ? "succeed" : "failed"); + logger.LogInformation("Navigate to {pageType} : {result}", pageType, navigated ? "succeed" : "failed"); } catch (Exception ex) { - logger.LogError(EventIds.NavigationFailed, ex, "An error occurred while navigating to {pageType}", pageType); + logger.LogError(ex, "An error occurred while navigating to {pageType}", pageType); infoBarService.Error(ex); } @@ -148,7 +148,7 @@ internal class NavigationService : INavigationService } catch (Exception ex) { - logger.LogError(EventIds.NavigationFailed, ex, "异步导航时发生异常"); + logger.LogError(ex, "异步导航时发生异常"); return NavigationResult.Failed; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index fb8f89b0..95c62397 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Snap.Hutao.Core.Database; +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Extension; using Snap.Hutao.Message; using Snap.Hutao.Model.Entity.Database; @@ -82,7 +83,7 @@ internal class UserService : IUserService } catch (InvalidOperationException ex) { - throw new Core.ExceptionService.UserdataCorruptedException($"用户 {currentUser.UserInfo?.Uid} 状态保存失败", ex); + ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceUserCurrentUpdateAndSaveFailed, currentUser.UserInfo?.Uid), ex); } } @@ -104,15 +105,10 @@ internal class UserService : IUserService await ThreadHelper.SwitchToBackgroundAsync(); using (IServiceScope scope = scopeFactory.CreateScope()) { - try - { - // Note: cascade deleted dailynotes - await scope.ServiceProvider.GetRequiredService().Users.RemoveAndSaveAsync(user.Entity).ConfigureAwait(false); - } - catch (DbUpdateConcurrencyException ex) - { - throw new Core.ExceptionService.UserdataCorruptedException("用户已被其他功能删除", ex); - } + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + await appDbContext.Users + .ExecuteDeleteWhereAsync(u => u.InnerId == user.Entity.InnerId) + .ConfigureAwait(false); } messenger.Send(new UserRemovedMessage(user.Entity)); @@ -144,7 +140,7 @@ internal class UserService : IUserService } catch (InvalidOperationException ex) { - throw new Core.ExceptionService.UserdataCorruptedException("无法设置当前用户", ex); + throw new UserdataCorruptedException(SH.ServiceUserCurrentMultiMatched, ex); } } @@ -201,7 +197,7 @@ internal class UserService : IUserService if (mid == null) { - return new(UserOptionResult.Invalid, "输入的Cookie无法获取用户信息"); + return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoMid); } // 检查 mid 对应用户是否存在 @@ -222,7 +218,7 @@ internal class UserService : IUserService } else { - return new(UserOptionResult.Invalid, "必须包含 Stoken"); + return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoStoken); } } } @@ -266,7 +262,7 @@ internal class UserService : IUserService } else { - return new(UserOptionResult.Invalid, "输入的 Cookie 无法获取用户信息"); + return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieRequestUserInfoFailed); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index e914cbcc..3fc0a68e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -174,13 +174,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -438,4 +438,20 @@ MSBuild:Compile + + + + + + True + True + SH.resx + + + + + ResXFileCodeGenerator + SH.Designer.cs + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs index 4d1ca8bb..3813892d 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs @@ -5,7 +5,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Navigation; using Microsoft.Web.WebView2.Core; using Snap.Hutao.Control; -using Snap.Hutao.Core; +using Snap.Hutao.Control.Theme; using Snap.Hutao.Service.Navigation; using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; using Windows.System; diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml index bd0b434d..3cad88ba 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml @@ -100,7 +100,6 @@ - diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml index 9cf104fe..6d9c6d9e 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml @@ -1,9 +1,10 @@ - - + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml.cs index ba0bc2b5..aa1d0702 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml.Navigation; +using Snap.Hutao.Control; using Snap.Hutao.Service.Navigation; using Snap.Hutao.ViewModel; @@ -10,25 +11,14 @@ namespace Snap.Hutao.View.Page; /// /// 设置页面 /// -public sealed partial class SettingPage : Microsoft.UI.Xaml.Controls.Page +public sealed partial class SettingPage : ScopedPage { /// /// 构造新的设置页面 /// public SettingPage() { - DataContext = Ioc.Default.GetRequiredService(); + InitializeWith(); InitializeComponent(); } - - /// - protected override void OnNavigatedTo(NavigationEventArgs e) - { - base.OnNavigatedTo(e); - - if (e.Parameter is INavigationData data) - { - data.NotifyNavigationCompleted(); - } - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml index 096fd6ea..99bfaa20 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml @@ -1,4 +1,4 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs index 8932e53b..954d4011 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml.Navigation; +using Snap.Hutao.Control; using Snap.Hutao.Service.Navigation; using Snap.Hutao.ViewModel; @@ -10,25 +11,14 @@ namespace Snap.Hutao.View.Page; /// /// 角色资料页 /// -public sealed partial class WikiAvatarPage : Microsoft.UI.Xaml.Controls.Page +public sealed partial class WikiAvatarPage : ScopedPage { /// /// 构造一个新的角色资料页 /// public WikiAvatarPage() { - DataContext = Ioc.Default.GetRequiredService(); + InitializeWith(); InitializeComponent(); } - - /// - protected override void OnNavigatedTo(NavigationEventArgs e) - { - base.OnNavigatedTo(e); - - if (e.Parameter is INavigationData data) - { - data.NotifyNavigationCompleted(); - } - } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs index 3c0a5159..1fe0cd93 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs @@ -26,9 +26,9 @@ public sealed partial class TitleView : UserControl public string Title { #if DEBUG - get => $"胡桃 Dev Build"; + get => string.Format(SH.AppDevNameAndVersion, Core.CoreEnvironment.Version); #else - get => $"胡桃 {Core.CoreEnvironment.Version}"; + get => string.Format(SH.AppNameAndVersion, Core.CoreEnvironment.Version); #endif } diff --git a/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml b/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml index 5088b8ce..3e5f2ef6 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml @@ -8,6 +8,7 @@ xmlns:shcb="using:Snap.Hutao.Control.Behavior" xmlns:shv="using:Snap.Hutao.ViewModel" d:DataContext="{d:DesignInstance shv:WelcomeViewModel}" + Unloaded="OnUnloaded" mc:Ignorable="d"> @@ -34,6 +35,7 @@ + diff --git a/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml.cs index ad2f8abf..b20040f2 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/WelcomeView.xaml.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.ViewModel; @@ -11,12 +12,21 @@ namespace Snap.Hutao.View; /// public sealed partial class WelcomeView : UserControl { + private readonly IServiceScope serviceScope; + /// /// һµĻӭͼ /// public WelcomeView() { InitializeComponent(); - DataContext = Ioc.Default.GetRequiredService(); + serviceScope = Ioc.Default.CreateScope(); + DataContext = serviceScope.ServiceProvider.GetRequiredService(); + } + + private void OnUnloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + { + DataContext = null; + serviceScope.Dispose(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs index 70e35cf1..a7585160 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs @@ -370,6 +370,12 @@ public class MiHoYoJSInterface } } + private IJsResult? LogUnhandledMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string message, params object?[] param) + { + logger.LogWarning(message, param); + return default; + } + private async Task TryGetJsResultFromJsParamAsync(JsParam param) { try @@ -391,7 +397,7 @@ public class MiHoYoJSInterface "login" => null, "pushPage" => await PushPageAsync(param).ConfigureAwait(false), "showLoading" => null, - _ => logger.LogWarning("Unhandled Message Type: {method}", param.Method), + _ => LogUnhandledMessage("Unhandled Message Type: {method}", param.Method), }; } catch (ObjectDisposedException) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs index 75618762..29b04df4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/HutaoEndpoints.cs @@ -116,6 +116,11 @@ internal static class HutaoEndpoints /// public static readonly Uri UIItemIconNone = new(StaticFile("Bg", "UI_ItemIcon_None.png")); + /// + /// UI_AvatarIcon_Side_None + /// + public static readonly Uri UIAvatarIconSideNone = new(StaticFile("AvatarIcon", "UI_AvatarIcon_Side_None.png")); + /// /// 压缩包资源 ///