mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
ConfigureAwait
This commit is contained in:
@@ -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]
|
||||
#### 命名样式 ####
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 继承自 <see cref="ContentDialog"/> 实现了某些便捷功能
|
||||
/// </summary>
|
||||
internal class ContentDialog2 : ContentDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的对话框
|
||||
/// </summary>
|
||||
/// <param name="window">窗口</param>
|
||||
public ContentDialog2(Window window)
|
||||
{
|
||||
DefaultStyleKey = typeof(ContentDialog);
|
||||
XamlRoot = window.Content.XamlRoot;
|
||||
Interaction.SetBehaviors(this, new() { new ContentDialogBehavior() });
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
/// </summary>
|
||||
internal static class ContentDialogExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 针对窗口进行初始化
|
||||
/// </summary>
|
||||
/// <param name="contentDialog">对话框</param>
|
||||
/// <param name="window">窗口</param>
|
||||
/// <returns>初始化完成的对话框</returns>
|
||||
public static ContentDialog InitializeWithWindow(this ContentDialog contentDialog, Window window)
|
||||
{
|
||||
contentDialog.XamlRoot = window.Content.XamlRoot;
|
||||
Interaction.SetBehaviors(contentDialog, new() { new ContentDialogBehavior() });
|
||||
|
||||
return contentDialog;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 阻止用户交互
|
||||
/// </summary>
|
||||
/// <param name="contentDialog">对话框</param>
|
||||
/// <param name="switchToMainThread">切换到主线程</param>
|
||||
/// <returns>用于恢复用户交互</returns>
|
||||
public static IDisposable BlockInteraction(this ContentDialog contentDialog)
|
||||
public static async ValueTask<IDisposable> BlockAsync(this ContentDialog contentDialog, bool switchToMainThread = false)
|
||||
{
|
||||
if (switchToMainThread)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
}
|
||||
|
||||
contentDialog.ShowAsync().AsTask().SafeForget();
|
||||
return new ContentDialogHider(contentDialog);
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,8 +15,7 @@ internal sealed partial class DatebaseLogger : ILogger
|
||||
/// Initializes a new instance of the <see cref="DatebaseLogger"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the logger.</param>
|
||||
/// <param name="logDbContext">应用程序数据库上下文</param>
|
||||
/// <param name="logDbContextLock">上下文锁</param>
|
||||
/// <param name="logEntryQueue">日志队列</param>
|
||||
public DatebaseLogger(string name, LogEntryQueue logEntryQueue)
|
||||
{
|
||||
this.name = name;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Threading.CodeAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// 在复杂的异步方法环境下
|
||||
/// 指示方法的线程访问状态
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
internal class ThreadAccessAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 指示方法的进入线程访问状态
|
||||
/// </summary>
|
||||
/// <param name="enter">进入状态</param>
|
||||
public ThreadAccessAttribute(ThreadAccessState enter)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指示方法的进入退出线程访问状态
|
||||
/// </summary>
|
||||
/// <param name="enter">进入状态</param>
|
||||
/// <param name="leave">离开状态</param>
|
||||
public ThreadAccessAttribute(ThreadAccessState enter, ThreadAccessState leave)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Threading.CodeAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// 线程访问情况
|
||||
/// </summary>
|
||||
internal enum ThreadAccessState
|
||||
{
|
||||
/// <summary>
|
||||
/// 任何线程均有可能访问该方法
|
||||
/// </summary>
|
||||
AnyThread,
|
||||
|
||||
/// <summary>
|
||||
/// 仅主线程有机会访问该方法
|
||||
/// 仅允许主线程访问该方法
|
||||
/// </summary>
|
||||
MainThread,
|
||||
}
|
||||
19
src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs
Normal file
19
src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// 线程帮助类
|
||||
/// </summary>
|
||||
internal static class ThreadHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步切换到主线程
|
||||
/// </summary>
|
||||
/// <returns>等待体</returns>
|
||||
public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
|
||||
{
|
||||
return new(Program.DispatcherQueue!);
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,8 @@ public class SystemBackdrop
|
||||
|
||||
backdropController = new();
|
||||
|
||||
// Mica Alt
|
||||
// backdropController.Kind = MicaKind.BaseAlt;
|
||||
backdropController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
|
||||
backdropController.SetSystemBackdropConfiguration(configuration);
|
||||
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public MainWindow(AppDbContext appDbContext, IMessenger messenger)
|
||||
public MainWindow(IMessenger messenger)
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
this.messenger = messenger;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Snap.Hutao.Message;
|
||||
/// <summary>
|
||||
/// 成就存档切换消息
|
||||
/// </summary>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||
internal class AchievementArchiveChangedMessage : ValueChangedMessage<AchievementArchive>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -18,16 +18,12 @@ namespace Snap.Hutao;
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
private static volatile DispatcherQueue? dispatcherQueue;
|
||||
|
||||
/// <summary>
|
||||
/// 异步切换到主线程
|
||||
/// 主线程队列
|
||||
/// </summary>
|
||||
/// <returns>等待体</returns>
|
||||
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<App>();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
/// <param name="newArchive">新存档</param>
|
||||
/// <returns>存档添加结果</returns>
|
||||
[ThreadAccess(ThreadAccessState.AnyThread)]
|
||||
Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive);
|
||||
}
|
||||
@@ -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
|
||||
/// <param name="delay">关闭延迟</param>
|
||||
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;
|
||||
|
||||
@@ -5,11 +5,13 @@ namespace Snap.Hutao.Service.Navigation;
|
||||
|
||||
/// <summary>
|
||||
/// 导航消息接收器
|
||||
/// 用于通知异步导航完成
|
||||
/// </summary>
|
||||
public interface INavigationRecipient
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步接收消息
|
||||
/// 异步接收导航消息
|
||||
/// 在此方法结束后才会通知导航服务导航完成
|
||||
/// </summary>
|
||||
/// <param name="data">导航数据</param>
|
||||
/// <returns>接收处理结果是否成功</returns>
|
||||
|
||||
@@ -44,6 +44,8 @@ public sealed partial class AchievementImportDialog : ContentDialog
|
||||
/// <returns>导入选项</returns>
|
||||
public async Task<ValueResult<bool, ImportOption>> GetImportOptionAsync()
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
ImportOption option = (ImportOption)ImportModeSelector.SelectedIndex;
|
||||
|
||||
|
||||
@@ -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<App>();
|
||||
|
||||
if (!ThemeHelper.Equals(current.RequestedTheme, RequestedTheme))
|
||||
{
|
||||
ILogger<MainView> logger = Ioc.Default.GetRequiredService<ILogger<MainView>>();
|
||||
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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
||||
{
|
||||
((AchievementViewModel)DataContext).SaveAchievements();
|
||||
|
||||
base.OnNavigatingFrom(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AchievementArchiveChangedMessage>,
|
||||
IRecipient<MainWindowClosedMessage>
|
||||
IDisposable,
|
||||
IRecipient<AchievementArchiveChangedMessage>
|
||||
{
|
||||
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<AchievementArchiveChangedMessage>(this);
|
||||
messenger.Register<MainWindowClosedMessage>(this);
|
||||
messenger.Register(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -190,22 +191,14 @@ internal class AchievementViewModel
|
||||
/// </summary>
|
||||
public ICommand SortIncompletedSwitchCommand { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Receive(MainWindowClosedMessage message)
|
||||
{
|
||||
SaveAchievements();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Receive(AchievementArchiveChangedMessage message)
|
||||
{
|
||||
HandleArchiveChangeAsync(message.OldValue, message.NewValue).SafeForget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前用户的成就
|
||||
/// </summary>
|
||||
public void SaveAchievements()
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Achievements != null && SelectedArchive != null)
|
||||
{
|
||||
@@ -216,11 +209,11 @@ internal class AchievementViewModel
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> 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<ContentDialogResult> ShowImportFailDialogAsync(string message)
|
||||
{
|
||||
return new ContentDialog2(Ioc.Default.GetRequiredService<MainWindow>())
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
Title = "导入失败",
|
||||
Content = message,
|
||||
PrimaryButtonText = "确认",
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
}.ShowAsync().AsTask();
|
||||
};
|
||||
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
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<AchievementGoal> goals = await metadataService.GetAchievementGoalsAsync(CancellationToken);
|
||||
List<AchievementGoal> 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<Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken);
|
||||
List<Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
|
||||
List<Model.Binding.Achievement> 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<MainWindow>();
|
||||
(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<MainWindow>();
|
||||
ContentDialogResult result = await new ContentDialog2(mainWindow)
|
||||
ContentDialog dialog = new()
|
||||
{
|
||||
Title = $"确定要删除存档 {SelectedArchive.Name} 吗?",
|
||||
Content = "该操作是不可逆的,该存档和其内的所有成就状态会丢失。",
|
||||
PrimaryButtonText = "确认",
|
||||
CloseButtonText = "取消",
|
||||
DefaultButton = ContentDialogButton.Close,
|
||||
}
|
||||
.ShowAsync();
|
||||
};
|
||||
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
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<UIAF?> GetUIAFFromClipboardAsync()
|
||||
{
|
||||
UIAF? uiaf = null;
|
||||
string json;
|
||||
try
|
||||
{
|
||||
json = await Clipboard.GetContent().GetTextAsync();
|
||||
Task<string> 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<UIAF?> GetUIAFFromFileAsync(StorageFile file)
|
||||
{
|
||||
UIAF? uiaf = null;
|
||||
@@ -414,7 +428,7 @@ internal class AchievementViewModel
|
||||
{
|
||||
using (Stream stream = fileSream.AsStream())
|
||||
{
|
||||
uiaf = await JsonSerializer.DeserializeAsync<UIAF>(stream, options);
|
||||
uiaf = await JsonSerializer.DeserializeAsync<UIAF>(stream, options).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,16 +440,18 @@ internal class AchievementViewModel
|
||||
return uiaf;
|
||||
}
|
||||
|
||||
[ThreadAccess(ThreadAccessState.AnyThread)]
|
||||
private async Task<bool> TryImportUIAFInternalAsync(Model.Entity.AchievementArchive archive, UIAF uiaf)
|
||||
{
|
||||
if (uiaf.IsCurrentVersionSupported())
|
||||
{
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
(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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -38,4 +38,4 @@ internal class GachaInfoClient
|
||||
{
|
||||
return httpClient.GetFromJsonAsync<Response<GachaLogPage>>(ApiEndpoints.GachaInfoGetGachaLog(config.AsQuery()), options, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,22 @@ public struct GachaLogConfigration
|
||||
{
|
||||
private readonly QueryString innerQuery;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的祈愿记录请求配置
|
||||
/// </summary>
|
||||
/// <param name="query">原始查询字符串</param>
|
||||
/// <param name="type">祈愿类型</param>
|
||||
/// <param name="endId">终止Id</param>
|
||||
public GachaLogConfigration(string query, GachaConfigType type, ulong endId = 0UL)
|
||||
{
|
||||
innerQuery = QueryString.Parse(query);
|
||||
innerQuery.Set("lang", "zh-cn");
|
||||
|
||||
Size = 20;
|
||||
Type = type;
|
||||
EndId = endId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尺寸
|
||||
/// </summary>
|
||||
@@ -36,22 +52,6 @@ public struct GachaLogConfigration
|
||||
set => innerQuery.Set("end_id", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的祈愿记录请求配置
|
||||
/// </summary>
|
||||
/// <param name="query">原始查询字符串</param>
|
||||
/// <param name="type">祈愿类型</param>
|
||||
/// <param name="endId">终止Id</param>
|
||||
public GachaLogConfigration(string query, GachaConfigType type, ulong endId = 0UL)
|
||||
{
|
||||
innerQuery = QueryString.Parse(query);
|
||||
innerQuery.Set("lang", "zh-cn");
|
||||
|
||||
Size = 20;
|
||||
Type = type;
|
||||
EndId = endId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换到查询字符串
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Snap.Hutao.Web;
|
||||
namespace Snap.Hutao.Web.Request;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="HttpRequestHeaders"/> 扩展
|
||||
Reference in New Issue
Block a user