diff --git a/src/Snap.Hutao/.editorconfig b/src/Snap.Hutao/.editorconfig
index 26eacb4e..2a13ab68 100644
--- a/src/Snap.Hutao/.editorconfig
+++ b/src/Snap.Hutao/.editorconfig
@@ -148,6 +148,18 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:silent
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+# CA1001: 具有可释放字段的类型应该是可释放的
+dotnet_diagnostic.CA1001.severity = warning
+
+# CA1309: 使用序数字符串比较
+dotnet_diagnostic.CA1309.severity = suggestion
+
+# CA1805: 避免进行不必要的初始化
+dotnet_diagnostic.CA1805.severity = suggestion
+
+# VSTHRD111: Use ConfigureAwait(bool)
+dotnet_diagnostic.VSTHRD111.severity = suggestion
+
[*.vb]
#### 命名样式 ####
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/ContentDialog2.cs b/src/Snap.Hutao/Snap.Hutao/Control/ContentDialog2.cs
deleted file mode 100644
index ce293009..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Control/ContentDialog2.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.Xaml.Interactivity;
-using Snap.Hutao.Control.Behavior;
-
-namespace Snap.Hutao.Control;
-
-///
-/// 继承自 实现了某些便捷功能
-///
-internal class ContentDialog2 : ContentDialog
-{
- ///
- /// 构造一个新的对话框
- ///
- /// 窗口
- public ContentDialog2(Window window)
- {
- DefaultStyleKey = typeof(ContentDialog);
- XamlRoot = window.Content.XamlRoot;
- Interaction.SetBehaviors(this, new() { new ContentDialogBehavior() });
- }
-}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs
index 514cb957..473310e5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs
@@ -1,7 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.Xaml.Interactivity;
+using Snap.Hutao.Control.Behavior;
+using Snap.Hutao.Core.Threading;
using Snap.Hutao.Extension;
namespace Snap.Hutao.Control.Extension;
@@ -11,13 +15,33 @@ namespace Snap.Hutao.Control.Extension;
///
internal static class ContentDialogExtensions
{
+ ///
+ /// 针对窗口进行初始化
+ ///
+ /// 对话框
+ /// 窗口
+ /// 初始化完成的对话框
+ public static ContentDialog InitializeWithWindow(this ContentDialog contentDialog, Window window)
+ {
+ contentDialog.XamlRoot = window.Content.XamlRoot;
+ Interaction.SetBehaviors(contentDialog, new() { new ContentDialogBehavior() });
+
+ return contentDialog;
+ }
+
///
/// 阻止用户交互
///
/// 对话框
+ /// 切换到主线程
/// 用于恢复用户交互
- public static IDisposable BlockInteraction(this ContentDialog contentDialog)
+ public static async ValueTask BlockAsync(this ContentDialog contentDialog, bool switchToMainThread = false)
{
+ if (switchToMainThread)
+ {
+ await ThreadHelper.SwitchToMainThreadAsync();
+ }
+
contentDialog.ShowAsync().AsTask().SafeForget();
return new ContentDialogHider(contentDialog);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs
index c861716b..91636fc1 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs
@@ -149,7 +149,7 @@ public class DescriptionTextBlock : ContentControl
private void OnActualThemeChanged(FrameworkElement sender, object args)
{
- ApplyDescription((TextBlock)this.Content, Description);
+ ApplyDescription((TextBlock)Content, Description);
}
[StructLayout(LayoutKind.Explicit)]
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
index b2a2882d..66feb7c5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.Windows.AppLifecycle;
+using Snap.Hutao.Core.Threading;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Navigation;
@@ -84,7 +85,7 @@ internal static class Activation
{
case "/import":
{
- await Program.SwitchToMainThreadAsync();
+ await ThreadHelper.SwitchToMainThreadAsync();
INavigationAwaiter navigationAwaiter = new NavigationExtra("InvokeByUri");
await Ioc.Default
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs
index 55f15da6..537bfd33 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs
@@ -15,8 +15,7 @@ internal sealed partial class DatebaseLogger : ILogger
/// Initializes a new instance of the class.
///
/// The name of the logger.
- /// 应用程序数据库上下文
- /// 上下文锁
+ /// 日志队列
public DatebaseLogger(string name, LogEntryQueue logEntryQueue)
{
this.name = name;
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessAttribute.cs
new file mode 100644
index 00000000..1129b130
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessAttribute.cs
@@ -0,0 +1,29 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Threading.CodeAnalysis;
+
+///
+/// 在复杂的异步方法环境下
+/// 指示方法的线程访问状态
+///
+[AttributeUsage(AttributeTargets.Method)]
+internal class ThreadAccessAttribute : Attribute
+{
+ ///
+ /// 指示方法的进入线程访问状态
+ ///
+ /// 进入状态
+ public ThreadAccessAttribute(ThreadAccessState enter)
+ {
+ }
+
+ ///
+ /// 指示方法的进入退出线程访问状态
+ ///
+ /// 进入状态
+ /// 离开状态
+ public ThreadAccessAttribute(ThreadAccessState enter, ThreadAccessState leave)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessState.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessState.cs
new file mode 100644
index 00000000..abd46b2a
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessState.cs
@@ -0,0 +1,21 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Threading.CodeAnalysis;
+
+///
+/// 线程访问情况
+///
+internal enum ThreadAccessState
+{
+ ///
+ /// 任何线程均有可能访问该方法
+ ///
+ AnyThread,
+
+ ///
+ /// 仅主线程有机会访问该方法
+ /// 仅允许主线程访问该方法
+ ///
+ MainThread,
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs
new file mode 100644
index 00000000..3090c0ca
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs
@@ -0,0 +1,19 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Threading;
+
+///
+/// 线程帮助类
+///
+internal static class ThreadHelper
+{
+ ///
+ /// 异步切换到主线程
+ ///
+ /// 等待体
+ public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
+ {
+ return new(Program.DispatcherQueue!);
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs
index 70d0edca..c4f00235 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs
@@ -57,6 +57,8 @@ public class SystemBackdrop
backdropController = new();
+ // Mica Alt
+ // backdropController.Kind = MicaKind.BaseAlt;
backdropController.AddSystemBackdropTarget(window.As());
backdropController.SetSystemBackdropConfiguration(configuration);
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
index 1f196e64..daac94af 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
@@ -15,7 +15,6 @@ namespace Snap.Hutao;
[Injection(InjectAs.Singleton)]
public sealed partial class MainWindow : Window
{
- private readonly AppDbContext appDbContext;
private readonly WindowManager windowManager;
private readonly IMessenger messenger;
@@ -26,9 +25,8 @@ public sealed partial class MainWindow : Window
///
/// 数据库上下文
/// 消息器
- public MainWindow(AppDbContext appDbContext, IMessenger messenger)
+ public MainWindow(IMessenger messenger)
{
- this.appDbContext = appDbContext;
this.messenger = messenger;
InitializeComponent();
diff --git a/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs
index 88af10bb..f263ca0d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Message;
///
/// 成就存档切换消息
///
+[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
internal class AchievementArchiveChangedMessage : ValueChangedMessage
{
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Program.cs b/src/Snap.Hutao/Snap.Hutao/Program.cs
index c0702732..0c36181c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Program.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Program.cs
@@ -18,16 +18,12 @@ namespace Snap.Hutao;
///
public static class Program
{
- private static volatile DispatcherQueue? dispatcherQueue;
-
///
- /// 异步切换到主线程
+ /// 主线程队列
///
- /// 等待体
- public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
- {
- return new(dispatcherQueue!);
- }
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [SuppressMessage("", "SA1401")]
+ internal static volatile DispatcherQueue? DispatcherQueue;
[DllImport("Microsoft.ui.xaml.dll")]
private static extern void XamlCheckProcessRequirements();
@@ -50,8 +46,8 @@ public static class Program
private static void InitializeApp(ApplicationInitializationCallbackParams param)
{
- dispatcherQueue = DispatcherQueue.GetForCurrentThread();
- DispatcherQueueSynchronizationContext context = new(dispatcherQueue);
+ DispatcherQueue = DispatcherQueue.GetForCurrentThread();
+ DispatcherQueueSynchronizationContext context = new(DispatcherQueue);
SynchronizationContext.SetSynchronizationContext(context);
_ = Ioc.Default.GetRequiredService();
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
index e68830cb..ff728d5f 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs
@@ -6,6 +6,7 @@ using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.Logging;
+using Snap.Hutao.Core.Threading;
using Snap.Hutao.Model.InterChange.Achievement;
using System.Collections.ObjectModel;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement;
@@ -87,6 +88,7 @@ internal class AchievementService : IAchievementService
else
{
// Sync cache
+ await ThreadHelper.SwitchToMainThreadAsync();
archiveCollection.Add(newArchive);
// Sync database
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs
index a6487c9d..dd166633 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Core.Threading.CodeAnalysis;
using Snap.Hutao.Model.InterChange.Achievement;
using System.Collections.ObjectModel;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement;
@@ -70,5 +71,6 @@ internal interface IAchievementService
///
/// 新存档
/// 存档添加结果
+ [ThreadAccess(ThreadAccessState.AnyThread)]
Task TryAddArchiveAsync(EntityArchive newArchive);
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs
index 8789c9f2..87ea680b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
+using Snap.Hutao.Core.Threading;
using Snap.Hutao.Service.Abstraction;
namespace Snap.Hutao.Service;
@@ -110,7 +111,7 @@ internal class InfoBarService : IInfoBarService
/// 关闭延迟
private async Task PrepareInfoBarAndShowInternalAsync(InfoBarSeverity severity, string? title, string? message, int delay)
{
- await Program.SwitchToMainThreadAsync();
+ await ThreadHelper.SwitchToMainThreadAsync();
InfoBar infoBar = new()
{
@@ -133,7 +134,7 @@ internal class InfoBarService : IInfoBarService
[SuppressMessage("", "VSTHRD100")]
private async void OnInfoBarClosed(InfoBar sender, InfoBarClosedEventArgs args)
{
- await Program.SwitchToMainThreadAsync();
+ await ThreadHelper.SwitchToMainThreadAsync();
Must.NotNull(infoBarStack!).Children.Remove(sender);
sender.Closed -= OnInfoBarClosed;
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationRecipient.cs b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationRecipient.cs
index 983e6438..4958951a 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationRecipient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationRecipient.cs
@@ -5,11 +5,13 @@ namespace Snap.Hutao.Service.Navigation;
///
/// 导航消息接收器
+/// 用于通知异步导航完成
///
public interface INavigationRecipient
{
///
- /// 异步接收消息
+ /// 异步接收导航消息
+ /// 在此方法结束后才会通知导航服务导航完成
///
/// 导航数据
/// 接收处理结果是否成功
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs
index 3c136174..d7cfe003 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs
@@ -44,6 +44,8 @@ public sealed partial class AchievementImportDialog : ContentDialog
/// 导入选项
public async Task> GetImportOptionAsync()
{
+ await ThreadHelper.SwitchToMainThreadAsync();
+
ContentDialogResult result = await ShowAsync();
ImportOption option = (ImportOption)ImportModeSelector.SelectedIndex;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
index 0bdeb2e5..d0f7f2b3 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs
@@ -4,6 +4,7 @@
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Logging;
+using Snap.Hutao.Core.Threading;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Navigation;
@@ -48,14 +49,16 @@ public sealed partial class MainView : UserControl
private async Task UpdateThemeAsync()
{
- await Program.SwitchToMainThreadAsync();
+ // It seems that UISettings.ColorValuesChanged
+ // event can raise up on a background thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
App current = Ioc.Default.GetRequiredService();
if (!ThemeHelper.Equals(current.RequestedTheme, RequestedTheme))
{
ILogger logger = Ioc.Default.GetRequiredService>();
- logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] App Theme [{app}]", RequestedTheme, current.RequestedTheme);
+ logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] | App Theme [{app}]", RequestedTheme, current.RequestedTheme);
// Update controls' theme which presents in the PopupRoot
RequestedTheme = ThemeHelper.ApplicationToElement(current.RequestedTheme);
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml.cs
index 2863c4d6..9b094105 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml.cs
@@ -32,18 +32,10 @@ public sealed partial class AchievementPage : CancellablePage
{
if (extra.Data != null)
{
- await ((INavigationRecipient)DataContext).ReceiveAsync(extra);
+ await ((INavigationRecipient)DataContext).ReceiveAsync(extra).ConfigureAwait(false);
}
extra.NotifyNavigationCompleted();
}
}
-
- ///
- protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
- {
- ((AchievementViewModel)DataContext).SaveAchievements();
-
- base.OnNavigatingFrom(e);
- }
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs
index 66133320..2e47f582 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs
@@ -6,9 +6,10 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.UI;
using Microsoft.UI.Xaml.Controls;
-using Snap.Hutao.Control;
using Snap.Hutao.Control.Cancellable;
using Snap.Hutao.Control.Extension;
+using Snap.Hutao.Core.Threading;
+using Snap.Hutao.Core.Threading.CodeAnalysis;
using Snap.Hutao.Extension;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Message;
@@ -37,10 +38,11 @@ internal class AchievementViewModel
: ObservableObject,
ISupportCancellation,
INavigationRecipient,
- IRecipient,
- IRecipient
+ IDisposable,
+ IRecipient
{
- private static readonly SortDescription IncompletedItemsFirstSortDescription = new(nameof(Model.Binding.Achievement.IsChecked), SortDirection.Ascending);
+ private static readonly SortDescription IncompletedItemsFirstSortDescription =
+ new(nameof(Model.Binding.Achievement.IsChecked), SortDirection.Ascending);
private readonly IMetadataService metadataService;
private readonly IAchievementService achievementService;
@@ -89,8 +91,7 @@ internal class AchievementViewModel
RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync);
SortIncompletedSwitchCommand = new RelayCommand(UpdateAchievementsSort);
- messenger.Register(this);
- messenger.Register(this);
+ messenger.Register(this);
}
///
@@ -190,22 +191,14 @@ internal class AchievementViewModel
///
public ICommand SortIncompletedSwitchCommand { get; }
- ///
- public void Receive(MainWindowClosedMessage message)
- {
- SaveAchievements();
- }
-
///
public void Receive(AchievementArchiveChangedMessage message)
{
HandleArchiveChangeAsync(message.OldValue, message.NewValue).SafeForget();
}
- ///
- /// 保存当前用户的成就
- ///
- public void SaveAchievements()
+ ///
+ public void Dispose()
{
if (Achievements != null && SelectedArchive != null)
{
@@ -216,11 +209,11 @@ internal class AchievementViewModel
///
public async Task ReceiveAsync(INavigationData data)
{
- if (await openUICompletionSource.Task)
+ if (await openUICompletionSource.Task.ConfigureAwait(false))
{
if (data.Data is "InvokeByUri")
{
- await ImportUIAFFromClipboardAsync();
+ await ImportUIAFFromClipboardAsync().ConfigureAwait(false);
return true;
}
}
@@ -228,17 +221,22 @@ internal class AchievementViewModel
return false;
}
+ [ThreadAccess(ThreadAccessState.MainThread)]
private static Task ShowImportFailDialogAsync(string message)
{
- return new ContentDialog2(Ioc.Default.GetRequiredService())
+ ContentDialog dialog = new()
{
Title = "导入失败",
Content = message,
PrimaryButtonText = "确认",
DefaultButton = ContentDialogButton.Primary,
- }.ShowAsync().AsTask();
+ };
+
+ MainWindow mainWindow = Ioc.Default.GetRequiredService();
+ return dialog.InitializeWithWindow(mainWindow).ShowAsync().AsTask();
}
+ [ThreadAccess(ThreadAccessState.MainThread)]
private async Task HandleArchiveChangeAsync(Model.Entity.AchievementArchive? oldArchieve, Model.Entity.AchievementArchive? newArchieve)
{
if (oldArchieve != null && Achievements != null)
@@ -248,17 +246,19 @@ internal class AchievementViewModel
if (newArchieve != null)
{
- await UpdateAchievementsAsync(newArchieve);
+ await UpdateAchievementsAsync(newArchieve).ConfigureAwait(false);
}
}
+ [ThreadAccess(ThreadAccessState.MainThread)]
private async Task OpenUIAsync()
{
- bool metaInitialized = await metadataService.InitializeAsync(CancellationToken);
+ bool metaInitialized = await metadataService.InitializeAsync(CancellationToken).ConfigureAwait(false);
if (metaInitialized)
{
- List goals = await metadataService.GetAchievementGoalsAsync(CancellationToken);
+ List goals = await metadataService.GetAchievementGoalsAsync(CancellationToken).ConfigureAwait(false);
+ await ThreadHelper.SwitchToMainThreadAsync();
AchievementGoals = goals.OrderBy(goal => goal.Order).ToList();
Archives = achievementService.GetArchiveCollection();
@@ -274,24 +274,29 @@ internal class AchievementViewModel
openUICompletionSource.TrySetResult(metaInitialized);
}
+ [ThreadAccess(ThreadAccessState.AnyThread)]
private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive)
{
- List rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken);
+ List rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List combined = achievementService.GetAchievements(archive, rawAchievements);
+
+ // Assemble achievements on the UI thread.
+ await ThreadHelper.SwitchToMainThreadAsync();
Achievements = new(combined, true);
UpdateAchievementFilter(SelectedAchievementGoal);
UpdateAchievementsSort();
}
+ [ThreadAccess(ThreadAccessState.MainThread)]
private async Task AddArchiveAsync()
{
MainWindow mainWindow = Ioc.Default.GetRequiredService();
- (bool isOk, string name) = await new AchievementArchiveCreateDialog(mainWindow).GetInputAsync();
+ (bool isOk, string name) = await new AchievementArchiveCreateDialog(mainWindow).GetInputAsync().ConfigureAwait(false);
if (isOk)
{
- ArchiveAddResult result = await achievementService.TryAddArchiveAsync(Model.Entity.AchievementArchive.Create(name));
+ ArchiveAddResult result = await achievementService.TryAddArchiveAsync(Model.Entity.AchievementArchive.Create(name)).ConfigureAwait(false);
switch (result)
{
@@ -310,24 +315,26 @@ internal class AchievementViewModel
}
}
+ [ThreadAccess(ThreadAccessState.MainThread)]
private async Task RemoveArchiveAsync()
{
if (Archives != null && SelectedArchive != null)
{
- MainWindow mainWindow = Ioc.Default.GetRequiredService();
- ContentDialogResult result = await new ContentDialog2(mainWindow)
+ ContentDialog dialog = new()
{
Title = $"确定要删除存档 {SelectedArchive.Name} 吗?",
Content = "该操作是不可逆的,该存档和其内的所有成就状态会丢失。",
PrimaryButtonText = "确认",
CloseButtonText = "取消",
DefaultButton = ContentDialogButton.Close,
- }
- .ShowAsync();
+ };
+
+ MainWindow mainWindow = Ioc.Default.GetRequiredService();
+ ContentDialogResult result = await dialog.InitializeWithWindow(mainWindow).ShowAsync();
if (result == ContentDialogResult.Primary)
{
- await achievementService.RemoveArchiveAsync(SelectedArchive);
+ await achievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
// reselect first archive
SelectedArchive = Archives.FirstOrDefault();
@@ -335,25 +342,28 @@ internal class AchievementViewModel
}
}
+ [ThreadAccess(ThreadAccessState.AnyThread)]
private async Task ImportUIAFFromClipboardAsync()
{
if (achievementService.CurrentArchive == null)
{
// TODO: automatically create a archive.
- infoBarService.Information("必须选择一个用户才能导入成就");
+ infoBarService.Information("必须创建一个用户才能导入成就");
return;
}
- if (await GetUIAFFromClipboardAsync() is UIAF uiaf)
+ if (await GetUIAFFromClipboardAsync().ConfigureAwait(false) is UIAF uiaf)
{
- await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf);
+ await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf).ConfigureAwait(false);
}
else
{
- await ShowImportFailDialogAsync("数据格式不正确");
+ await ThreadHelper.SwitchToMainThreadAsync();
+ await ShowImportFailDialogAsync("数据格式不正确").ConfigureAwait(false);
}
}
+ [ThreadAccess(ThreadAccessState.MainThread)]
private async Task ImportUIAFFromFileAsync()
{
if (achievementService.CurrentArchive == null)
@@ -368,24 +378,27 @@ internal class AchievementViewModel
if (await picker.PickSingleFileAsync() is StorageFile file)
{
- if (await GetUIAFFromFileAsync(file) is UIAF uiaf)
+ if (await GetUIAFFromFileAsync(file).ConfigureAwait(false) is UIAF uiaf)
{
- await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf);
+ await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf).ConfigureAwait(false);
}
else
{
- await ShowImportFailDialogAsync("数据格式不正确");
+ await ThreadHelper.SwitchToMainThreadAsync();
+ await ShowImportFailDialogAsync("数据格式不正确").ConfigureAwait(false);
}
}
}
+ [ThreadAccess(ThreadAccessState.AnyThread)]
private async Task GetUIAFFromClipboardAsync()
{
UIAF? uiaf = null;
string json;
try
{
- json = await Clipboard.GetContent().GetTextAsync();
+ Task task = Clipboard.GetContent().GetTextAsync().AsTask();
+ json = await task.ConfigureAwait(false);
}
catch (COMException ex)
{
@@ -405,6 +418,7 @@ internal class AchievementViewModel
return uiaf;
}
+ [ThreadAccess(ThreadAccessState.AnyThread)]
private async Task GetUIAFFromFileAsync(StorageFile file)
{
UIAF? uiaf = null;
@@ -414,7 +428,7 @@ internal class AchievementViewModel
{
using (Stream stream = fileSream.AsStream())
{
- uiaf = await JsonSerializer.DeserializeAsync(stream, options);
+ uiaf = await JsonSerializer.DeserializeAsync(stream, options).ConfigureAwait(false);
}
}
}
@@ -426,16 +440,18 @@ internal class AchievementViewModel
return uiaf;
}
+ [ThreadAccess(ThreadAccessState.AnyThread)]
private async Task TryImportUIAFInternalAsync(Model.Entity.AchievementArchive archive, UIAF uiaf)
{
if (uiaf.IsCurrentVersionSupported())
{
MainWindow mainWindow = Ioc.Default.GetRequiredService();
- (bool isOk, ImportOption option) = await new AchievementImportDialog(mainWindow, uiaf).GetImportOptionAsync();
+ await ThreadHelper.SwitchToMainThreadAsync();
+ (bool isOk, ImportOption option) = await new AchievementImportDialog(mainWindow, uiaf).GetImportOptionAsync().ConfigureAwait(true);
if (isOk)
{
- ContentDialog2 importingDialog = new(mainWindow)
+ ContentDialog importingDialog = new()
{
Title = "导入成就中",
Content = new ProgressBar() { IsIndeterminate = true },
@@ -443,19 +459,20 @@ internal class AchievementViewModel
};
ImportResult result;
- using (importingDialog.BlockInteraction())
+ using (await importingDialog.InitializeWithWindow(mainWindow).BlockAsync().ConfigureAwait(false))
{
- result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, option);
+ result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, option).ConfigureAwait(false);
}
infoBarService.Success(result.ToString());
- await UpdateAchievementsAsync(archive);
+ await UpdateAchievementsAsync(archive).ConfigureAwait(false);
return true;
}
}
else
{
- await ShowImportFailDialogAsync("数据的 UIAF 版本过低,无法导入");
+ await ThreadHelper.SwitchToMainThreadAsync();
+ await ShowImportFailDialogAsync("数据的 UIAF 版本过低,无法导入").ConfigureAwait(false);
}
return false;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs
index 61c9088f..7dd31c13 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs
@@ -3,6 +3,7 @@
using System.Net.Http;
using System.Net.Http.Json;
+using Snap.Hutao.Web.Request;
namespace Snap.Hutao.Web.Hoyolab.DynamicSecret.Http;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs
index 977d3b24..6c376957 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs
@@ -38,4 +38,4 @@ internal class GachaInfoClient
{
return httpClient.GetFromJsonAsync>(ApiEndpoints.GachaInfoGetGachaLog(config.AsQuery()), options, token);
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogConfigration.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogConfigration.cs
index b1eb1b5e..5c56d8e6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogConfigration.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogConfigration.cs
@@ -12,6 +12,22 @@ public struct GachaLogConfigration
{
private readonly QueryString innerQuery;
+ ///
+ /// 构造一个新的祈愿记录请求配置
+ ///
+ /// 原始查询字符串
+ /// 祈愿类型
+ /// 终止Id
+ public GachaLogConfigration(string query, GachaConfigType type, ulong endId = 0UL)
+ {
+ innerQuery = QueryString.Parse(query);
+ innerQuery.Set("lang", "zh-cn");
+
+ Size = 20;
+ Type = type;
+ EndId = endId;
+ }
+
///
/// 尺寸
///
@@ -36,22 +52,6 @@ public struct GachaLogConfigration
set => innerQuery.Set("end_id", value);
}
- ///
- /// 构造一个新的祈愿记录请求配置
- ///
- /// 原始查询字符串
- /// 祈愿类型
- /// 终止Id
- public GachaLogConfigration(string query, GachaConfigType type, ulong endId = 0UL)
- {
- innerQuery = QueryString.Parse(query);
- innerQuery.Set("lang", "zh-cn");
-
- Size = 20;
- Type = type;
- EndId = endId;
- }
-
///
/// 转换到查询字符串
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientCookieExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientCookieExtensions.cs
index 9e8d3762..8137c8d9 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientCookieExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientCookieExtensions.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding;
+using Snap.Hutao.Web.Request;
using System.Net.Http;
namespace Snap.Hutao.Web.Hoyolab;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/HttpRequestHeadersExtensions.cs
similarity index 94%
rename from src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs
rename to src/Snap.Hutao/Snap.Hutao/Web/Request/HttpRequestHeadersExtensions.cs
index 38329d90..eceaa901 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/HttpRequestHeadersExtensions.cs
@@ -3,7 +3,7 @@
using System.Net.Http.Headers;
-namespace Snap.Hutao.Web;
+namespace Snap.Hutao.Web.Request;
///
/// 扩展