ConfigureAwait

This commit is contained in:
DismissedLight
2022-09-08 21:29:31 +08:00
parent a34f24839b
commit e957ad07a1
26 changed files with 221 additions and 122 deletions

View File

@@ -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]
#### 命名样式 ####

View File

@@ -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() });
}
}

View File

@@ -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);
}

View File

@@ -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)]

View File

@@ -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

View File

@@ -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;

View File

@@ -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)
{
}
}

View File

@@ -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,
}

View 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!);
}
}

View File

@@ -57,6 +57,8 @@ public class SystemBackdrop
backdropController = new();
// Mica Alt
// backdropController.Kind = MicaKind.BaseAlt;
backdropController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
backdropController.SetSystemBackdropConfiguration(configuration);

View File

@@ -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();

View File

@@ -8,6 +8,7 @@ namespace Snap.Hutao.Message;
/// <summary>
/// 成就存档切换消息
/// </summary>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
internal class AchievementArchiveChangedMessage : ValueChangedMessage<AchievementArchive>
{
/// <summary>

View File

@@ -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>();

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -5,11 +5,13 @@ namespace Snap.Hutao.Service.Navigation;
/// <summary>
/// 导航消息接收器
/// 用于通知异步导航完成
/// </summary>
public interface INavigationRecipient
{
/// <summary>
/// 异步接收消息
/// 异步接收导航消息
/// 在此方法结束后才会通知导航服务导航完成
/// </summary>
/// <param name="data">导航数据</param>
/// <returns>接收处理结果是否成功</returns>

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -38,4 +38,4 @@ internal class GachaInfoClient
{
return httpClient.GetFromJsonAsync<Response<GachaLogPage>>(ApiEndpoints.GachaInfoGetGachaLog(config.AsQuery()), options, token);
}
}
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -3,7 +3,7 @@
using System.Net.Http.Headers;
namespace Snap.Hutao.Web;
namespace Snap.Hutao.Web.Request;
/// <summary>
/// <see cref="HttpRequestHeaders"/> 扩展