diff --git a/src/Snap.Hutao/Snap.Hutao/.filenesting.json b/src/Snap.Hutao/Snap.Hutao/.filenesting.json index d1473ce7..f88118b1 100644 --- a/src/Snap.Hutao/Snap.Hutao/.filenesting.json +++ b/src/Snap.Hutao/Snap.Hutao/.filenesting.json @@ -9,7 +9,7 @@ }, "pathSegment": { "add": { - ".*": [ ".cs" ] + ".*": [ ".cs", ".resx" ] } }, "fileSuffixToExtension": { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/EnumerableExtension.cs new file mode 100644 index 00000000..696de653 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/EnumerableExtension.cs @@ -0,0 +1,22 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Database; + +/// +/// 可枚举扩展 +/// +public static class EnumerableExtension +{ + /// + /// 获取选中的值或默认值 + /// + /// 源类型 + /// 源 + /// 选中的值或默认值 + public static TSource? SelectedOrDefault(this IEnumerable source) + where TSource : ISelectable + { + return source.SingleOrDefault(i => i.IsSelected); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs index 357252dd..8f5c2507 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs @@ -17,6 +17,7 @@ using Snap.Hutao.Model.InterChange.GachaLog; using Snap.Hutao.Model.Metadata.Abstraction; using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.GachaLog.Factory; +using Snap.Hutao.Service.GachaLog.QueryProvider; using Snap.Hutao.Service.Metadata; using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; using Snap.Hutao.Web.Response; @@ -43,7 +44,7 @@ internal class GachaLogService : IGachaLogService }.ToImmutableList(); private readonly AppDbContext appDbContext; - private readonly IEnumerable urlProviders; + private readonly IEnumerable urlProviders; private readonly GachaInfoClient gachaInfoClient; private readonly IMetadataService metadataService; private readonly IGachaStatisticsFactory gachaStatisticsFactory; @@ -72,7 +73,7 @@ internal class GachaLogService : IGachaLogService /// 消息器 public GachaLogService( AppDbContext appDbContext, - IEnumerable urlProviders, + IEnumerable urlProviders, GachaInfoClient gachaInfoClient, IMetadataService metadataService, IGachaStatisticsFactory gachaStatisticsFactory, @@ -172,7 +173,7 @@ internal class GachaLogService : IGachaLogService } /// - public IGachaLogUrlProvider? GetGachaLogUrlProvider(RefreshOption option) + public IGachaLogQueryProvider? GetGachaLogQueryProvider(RefreshOption option) { return option switch { @@ -207,7 +208,7 @@ internal class GachaLogService : IGachaLogService } /// - public async Task RefreshGachaLogAsync(string query, RefreshStrategy strategy, IProgress progress, CancellationToken token) + public async Task RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress progress, CancellationToken token) { bool isLazy = strategy switch { @@ -241,7 +242,7 @@ internal class GachaLogService : IGachaLogService return Task.Delay(TimeSpan.FromSeconds(Random.Shared.NextDouble() + 1), token); } - private async Task> FetchGachaLogsAsync(string query, bool isLazy, IProgress progress, CancellationToken token) + private async Task> FetchGachaLogsAsync(GachaLogQuery query, bool isLazy, IProgress progress, CancellationToken token) { GachaArchive? archive = null; FetchState state = new(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs index f09f5e14..f89bd435 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/IGachaLogService.cs @@ -4,6 +4,7 @@ using Snap.Hutao.Model.Binding.Gacha; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.InterChange.GachaLog; +using Snap.Hutao.Service.GachaLog.QueryProvider; using System.Collections.ObjectModel; namespace Snap.Hutao.Service.GachaLog; @@ -36,7 +37,7 @@ internal interface IGachaLogService /// /// 刷新模式 /// 祈愿日志Url提供器 - IGachaLogUrlProvider? GetGachaLogUrlProvider(RefreshOption option); + IGachaLogQueryProvider? GetGachaLogQueryProvider(RefreshOption option); /// /// 获得对应的祈愿统计 @@ -69,7 +70,7 @@ internal interface IGachaLogService /// 进度 /// 取消令牌 /// 验证密钥是否可用 - Task RefreshGachaLogAsync(string query, RefreshStrategy strategy, IProgress progress, CancellationToken token); + Task RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress progress, CancellationToken token); /// /// 删除存档 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuery.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuery.cs new file mode 100644 index 00000000..ad89ddbd --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogQuery.cs @@ -0,0 +1,52 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.GachaLog.QueryProvider; + +/// +/// 祈愿记录query +/// +internal readonly struct GachaLogQuery +{ + /// + /// query + /// + public readonly string Query; + + /// + /// 是否为国际服 + /// + public readonly bool IsOversea; + + /// + /// 消息 + /// + public readonly string Message; + + /// + /// 构造一个新的祈愿记录query + /// + /// query + /// 是否为国际服 + public GachaLogQuery(string query, bool isOversea) + { + Query = query; + IsOversea = isOversea; + Message = string.Empty; + } + + /// + /// 构造一个新的失败的祈愿记录query + /// + /// 失败原因 + public GachaLogQuery(string message) + { + Message = message; + Query = string.Empty; + } + + public static implicit operator GachaLogQuery(string message) + { + return new(message); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogUrlManualInputProvider.cs similarity index 52% rename from src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs rename to src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogUrlManualInputProvider.cs index 850ba224..67f4ee5e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlManualInputProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogUrlManualInputProvider.cs @@ -3,29 +3,29 @@ using Snap.Hutao.View.Dialog; -namespace Snap.Hutao.Service.GachaLog; +namespace Snap.Hutao.Service.GachaLog.QueryProvider; /// /// 手动输入方法 /// -[Injection(InjectAs.Transient, typeof(IGachaLogUrlProvider))] -internal class GachaLogUrlManualInputProvider : IGachaLogUrlProvider +[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))] +internal class GachaLogUrlManualInputProvider : IGachaLogQueryProvider { /// public string Name { get => nameof(GachaLogUrlManualInputProvider); } /// - public async Task> GetQueryAsync() + public async Task> GetQueryAsync() { // ContentDialog must be created by main thread. await ThreadHelper.SwitchToMainThreadAsync(); - ValueResult result = await new GachaLogUrlDialog().GetInputUrlAsync().ConfigureAwait(false); + (bool isOk, string query) = await new GachaLogUrlDialog().GetInputUrlAsync().ConfigureAwait(false); - if (result.IsOk) + if (isOk) { - if (result.Value.Contains("&auth_appid=webview_gacha")) + if (query.Contains("&auth_appid=webview_gacha")) { - return result; + return new(true, new(query, query.Contains("hoyoverse.com"))); } else { @@ -34,7 +34,7 @@ internal class GachaLogUrlManualInputProvider : IGachaLogUrlProvider } else { - return new(false, null!); + return new(false, string.Empty); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogUrlStokenProvider.cs similarity index 81% rename from src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs rename to src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogUrlStokenProvider.cs index 231674a3..256534e2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlStokenProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogUrlStokenProvider.cs @@ -7,13 +7,13 @@ 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; +namespace Snap.Hutao.Service.GachaLog.QueryProvider; /// /// 使用Stokn提供祈愿Url /// -[Injection(InjectAs.Transient, typeof(IGachaLogUrlProvider))] -internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider +[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))] +internal class GachaLogUrlStokenProvider : IGachaLogQueryProvider { private readonly IUserService userService; private readonly BindingClient2 bindingClient2; @@ -33,7 +33,7 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider public string Name { get => nameof(GachaLogUrlStokenProvider); } /// - public async Task> GetQueryAsync() + public async Task> GetQueryAsync() { if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) { @@ -42,7 +42,7 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider if (authkeyResponse.IsOk()) { - return new(true, GachaLogQueryOptions.AsQuery(data, authkeyResponse.Data)); + return new(true, new(GachaLogQueryOptions.AsQuery(data, authkeyResponse.Data), false)); } else { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogUrlWebCacheProvider.cs similarity index 81% rename from src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs rename to src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogUrlWebCacheProvider.cs index d1a8bdee..f337427d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/GachaLogUrlWebCacheProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/GachaLogUrlWebCacheProvider.cs @@ -6,13 +6,13 @@ using Snap.Hutao.Service.Game; using System.IO; using System.Text; -namespace Snap.Hutao.Service.GachaLog; +namespace Snap.Hutao.Service.GachaLog.QueryProvider; /// /// 浏览器缓存方法 /// -[Injection(InjectAs.Transient, typeof(IGachaLogUrlProvider))] -internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider +[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))] +internal class GachaLogUrlWebCacheProvider : IGachaLogQueryProvider { private readonly IGameService gameService; @@ -44,7 +44,7 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider } /// - public async Task> GetQueryAsync() + public async Task> GetQueryAsync() { (bool isOk, string path) = await gameService.GetGamePathAsync().ConfigureAwait(false); @@ -65,7 +65,15 @@ internal class GachaLogUrlWebCacheProvider : IGachaLogUrlProvider { await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); string? result = Match(memoryStream, cacheFile.Contains(GameConstants.GenshinImpactData)); - return new(!string.IsNullOrEmpty(result), result ?? SH.ServiceGachaLogUrlProviderCacheUrlNotFound); + + if (!string.IsNullOrEmpty(result)) + { + return new(true, new(result, result.Contains("hoyoverse.com"))); + } + else + { + return new(false, SH.ServiceGachaLogUrlProviderCacheUrlNotFound); + } } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/IGachaLogUrlProvider.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProvider.cs similarity index 70% rename from src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/IGachaLogUrlProvider.cs rename to src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProvider.cs index a88d6dfc..8e2cf63a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/UrlProvider/IGachaLogUrlProvider.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/QueryProvider/IGachaLogQueryProvider.cs @@ -3,17 +3,17 @@ using Snap.Hutao.Core.Abstraction; -namespace Snap.Hutao.Service.GachaLog; +namespace Snap.Hutao.Service.GachaLog.QueryProvider; /// /// 祈愿记录Url提供器 /// -internal interface IGachaLogUrlProvider : INamed +internal interface IGachaLogQueryProvider : INamed { /// /// 异步获取包含验证密钥的查询语句 /// 查询语句可以仅包含?后的内容 /// /// 包含验证密钥的查询语句 - Task> GetQueryAsync(); -} + Task> GetQueryAsync(); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml index 02da1683..f616e076 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml @@ -166,13 +166,11 @@ - + diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs index bdf7e987..e83e5d3f 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Abstraction/ViewModel.cs @@ -26,6 +26,19 @@ public abstract class ViewModel : ObservableObject, IViewModel /// public bool IsViewDisposed { get; set; } + /// + /// 保证 using scope 内的代码运行完成 + /// 防止 视图资源被回收 + /// + /// 解除执行限制 + protected async Task EnterCriticalExecutionAsync() + { + ThrowIfViewDisposed(); + IDisposable disposable = await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false); + ThrowIfViewDisposed(); + return disposable; + } + /// /// 当页面被释放后抛出异常 /// diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs index a40fda32..f9d59bdf 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs @@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.WinUI.UI; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Control.Extension; +using Snap.Hutao.Core.Database; using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO.DataTransfer; using Snap.Hutao.Core.LifeCycle; @@ -23,6 +24,7 @@ using Windows.Storage.Pickers; using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement; using BindingAchievementGoal = Snap.Hutao.Model.Binding.Achievement.AchievementGoal; using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive; +using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement; using MetadataAchievementGoal = Snap.Hutao.Model.Metadata.Achievement.AchievementGoal; namespace Snap.Hutao.ViewModel; @@ -246,11 +248,8 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien List sortedGoals; ObservableCollection archives; - ThrowIfViewDisposed(); - using (await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false)) + using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) { - ThrowIfViewDisposed(); - List goals = await metadataService.GetAchievementGoalsAsync(CancellationToken).ConfigureAwait(false); sortedGoals = goals .OrderBy(goal => goal.Order) @@ -262,7 +261,7 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien await ThreadHelper.SwitchToMainThreadAsync(); AchievementGoals = sortedGoals; Archives = archives; - SelectedArchive = Archives.SingleOrDefault(a => a.IsSelected == true); + SelectedArchive = Archives.SelectedOrDefault(); IsInitialized = true; } @@ -271,6 +270,7 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien // User canceled the loading operation, // Indicate initialization not succeed. openUICompletionSource.TrySetResult(false); + return; } } @@ -343,30 +343,6 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien } #endregion - private void SearchAchievement(string? search) - { - if (Achievements != null) - { - SetProperty(ref selectedAchievementGoal, null); - - if (!string.IsNullOrEmpty(search)) - { - if (search.Length == 5 && int.TryParse(search, out int achiId)) - { - Achievements.Filter = (object o) => ((BindingAchievement)o).Inner.Id == achiId; - } - else - { - Achievements.Filter = (object o) => - { - BindingAchievement achi = (BindingAchievement)o; - return achi.Inner.Title.Contains(search) || achi.Inner.Description.Contains(search); - }; - } - } - } - } - #region 导入导出 private async Task ExportAsUIAFToFileAsync() { @@ -492,9 +468,33 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien } #endregion + private void SearchAchievement(string? search) + { + if (Achievements != null) + { + SetProperty(ref selectedAchievementGoal, null); + + if (!string.IsNullOrEmpty(search)) + { + if (search.Length == 5 && int.TryParse(search, out int achiId)) + { + Achievements.Filter = obj => ((BindingAchievement)obj).Inner.Id == achiId; + } + else + { + Achievements.Filter = obj => + { + BindingAchievement achi = (BindingAchievement)obj; + return achi.Inner.Title.Contains(search) || achi.Inner.Description.Contains(search); + }; + } + } + } + } + private async Task UpdateAchievementsAsync(EntityAchievementArchive archive) { - List rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false); + List rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false); List combined; try { @@ -547,22 +547,22 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien int count = 0; if (Achievements != null && AchievementGoals != null) { - Dictionary counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalAggregation(x)); - foreach (BindingAchievement achievement in Achievements.SourceCollection.OfType()) + Dictionary counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalStatistics(x)); + foreach (BindingAchievement achievement in Achievements.SourceCollection.Cast()) { - ref GoalAggregation aggregation = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal); - aggregation.Count += 1; + ref GoalStatistics stat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal); + stat.Count += 1; count += 1; if (achievement.IsChecked) { - aggregation.Finished += 1; + stat.Finished += 1; finished += 1; } } - foreach (GoalAggregation aggregation1 in counter.Values) + foreach (GoalStatistics statistics in counter.Values) { - aggregation1.AchievementGoal.UpdateFinishPercent(aggregation1.Finished, aggregation1.Count); + statistics.AchievementGoal.UpdateFinishPercent(statistics.Finished, statistics.Count); } FinishDescription = $"{finished}/{count} - {(double)finished / count:P2}"; @@ -578,13 +578,13 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien } } - private struct GoalAggregation + private struct GoalStatistics { public readonly BindingAchievementGoal AchievementGoal; public int Finished; public int Count; - public GoalAggregation(BindingAchievementGoal goal) + public GoalStatistics(BindingAchievementGoal goal) { AchievementGoal = goal; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs index 98c7df89..637a1434 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs @@ -124,13 +124,9 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel private Task RefreshByEnkaApiAsync() { - if (userService.Current is User user) + if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) { - if (user.SelectedUserGameRole is UserGameRole role) - { - UserAndUid userAndUid = new(user.Entity, role); - return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromEnkaAPI, CancellationToken); - } + return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromEnkaAPI, CancellationToken); } return Task.CompletedTask; @@ -138,13 +134,9 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel private Task RefreshByHoyolabGameRecordAsync() { - if (userService.Current is User user) + if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) { - if (user.SelectedUserGameRole is UserGameRole role) - { - UserAndUid userAndUid = new(user.Entity, role); - return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabGameRecord, CancellationToken); - } + return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabGameRecord, CancellationToken); } return Task.CompletedTask; @@ -152,13 +144,9 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel private Task RefreshByHoyolabCalculateAsync() { - if (userService.Current is User user) + if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) { - if (user.SelectedUserGameRole is UserGameRole role) - { - UserAndUid userAndUid = new(user.Entity, role); - return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabCalculate, CancellationToken); - } + return RefreshCoreAsync(userAndUid, RefreshOption.RequestFromHoyolabCalculate, CancellationToken); } return Task.CompletedTask; @@ -169,10 +157,8 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel try { ValueResult summaryResult; - ThrowIfViewDisposed(); - using (await DisposeLock.EnterAsync(token).ConfigureAwait(false)) + using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) { - ThrowIfViewDisposed(); ContentDialog dialog = await contentDialogFactory .CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyFetch) .ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs index 513de5c4..aaadeb3a 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNoteViewModel.cs @@ -175,7 +175,7 @@ internal class DailyNoteViewModel : Abstraction.ViewModel { try { - UserAndUids = await userService.GetRoleCollectionAsync().ConfigureAwait(false); + UserAndUids = await userService.GetRoleCollectionAsync().ConfigureAwait(true); } catch (Core.ExceptionService.UserdataCorruptedException ex) { @@ -185,10 +185,8 @@ internal class DailyNoteViewModel : Abstraction.ViewModel try { - ThrowIfViewDisposed(); - using (await DisposeLock.EnterAsync().ConfigureAwait(false)) + using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) { - ThrowIfViewDisposed(); await ThreadHelper.SwitchToMainThreadAsync(); refreshSecondsEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteRefreshSeconds, "480"); @@ -203,10 +201,9 @@ internal class DailyNoteViewModel : Abstraction.ViewModel silentModeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteSilentWhenPlayingGame, SettingEntryHelper.FalseString); isSilentWhenPlayingGame = silentModeEntry.GetBoolean(); OnPropertyChanged(nameof(IsSilentWhenPlayingGame)); - - await ThreadHelper.SwitchToBackgroundAsync(); } + await ThreadHelper.SwitchToBackgroundAsync(); ObservableCollection temp = await dailyNoteService.GetDailyNoteEntriesAsync().ConfigureAwait(false); await ThreadHelper.SwitchToMainThreadAsync(); DailyNoteEntries = temp; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs index 577a2a4b..671ff11b 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLogViewModel.cs @@ -12,6 +12,7 @@ using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.InterChange.GachaLog; using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.GachaLog; +using Snap.Hutao.Service.GachaLog.QueryProvider; using Snap.Hutao.View.Dialog; using System.Collections.ObjectModel; using Windows.Storage.Pickers; @@ -183,11 +184,11 @@ internal class GachaLogViewModel : Abstraction.ViewModel private async Task RefreshInternalAsync(RefreshOption option) { - IGachaLogUrlProvider? provider = gachaLogService.GetGachaLogUrlProvider(option); + IGachaLogQueryProvider? provider = gachaLogService.GetGachaLogQueryProvider(option); if (provider != null) { - (bool isOk, string query) = await provider.GetQueryAsync().ConfigureAwait(false); + (bool isOk, GachaLogQuery query) = await provider.GetQueryAsync().ConfigureAwait(false); if (isOk) { @@ -265,31 +266,29 @@ internal class GachaLogViewModel : Abstraction.ViewModel private async Task ExportToUIGFJsonAsync() { - if (SelectedArchive == null) + if (SelectedArchive != null) { - return; - } + FileSavePicker picker = pickerFactory.GetFileSavePicker(); + picker.SuggestedStartLocation = PickerLocationId.Desktop; + picker.SuggestedFileName = SelectedArchive.Uid; + picker.CommitButtonText = SH.FilePickerExportCommit; + picker.FileTypeChoices.Add(SH.ViewModelGachaLogExportFileType, ".json".Enumerate().ToList()); - FileSavePicker picker = pickerFactory.GetFileSavePicker(); - picker.SuggestedStartLocation = PickerLocationId.Desktop; - picker.SuggestedFileName = SelectedArchive.Uid; - picker.CommitButtonText = SH.FilePickerExportCommit; - picker.FileTypeChoices.Add(SH.ViewModelGachaLogExportFileType, ".json".Enumerate().ToList()); + (bool isPickerOk, FilePath file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false); - (bool isPickerOk, FilePath file) = await picker.TryPickSaveFileAsync().ConfigureAwait(false); - - if (isPickerOk) - { - UIGF uigf = await gachaLogService.ExportToUIGFAsync(SelectedArchive).ConfigureAwait(false); - bool isOk = await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false); - - if (isOk) + if (isPickerOk) { - infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage); - } - else - { - infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage); + UIGF uigf = await gachaLogService.ExportToUIGFAsync(SelectedArchive).ConfigureAwait(false); + bool isOk = await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false); + + if (isOk) + { + infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage); + } + else + { + infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage); + } } } } 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 e3aa3858..bf9bf77d 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 @@ -40,8 +40,7 @@ internal class GachaInfoClient { string query = config.AsQuery(); - // TODO: fix oversea behavior - string url = query.Contains("hoyoverse.com") + string url = config.IsOversea ? ApiOsEndpoints.GachaInfoGetGachaLog(query) : ApiEndpoints.GachaInfoGetGachaLog(query); diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogQueryOptions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogQueryOptions.cs index 65cb4a3d..9b1a5363 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogQueryOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogQueryOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Service.GachaLog.QueryProvider; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Web.Request.QueryString; @@ -9,13 +10,18 @@ namespace Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; /// /// 祈愿记录请求配置 /// -public struct GachaLogQueryOptions +internal struct GachaLogQueryOptions { /// /// 尺寸 /// public const int Size = 20; + /// + /// 是否为国际服 + /// + public readonly bool IsOversea; + /// /// Keys required: /// authkey_ver @@ -36,9 +42,10 @@ public struct GachaLogQueryOptions /// 原始查询字符串 /// 祈愿类型 /// 终止Id - public GachaLogQueryOptions(string query, GachaConfigType type, long endId = 0L) + public GachaLogQueryOptions(GachaLogQuery query, GachaConfigType type, long endId = 0L) { - innerQuery = QueryString.Parse(query); + IsOversea = query.IsOversea; + innerQuery = QueryString.Parse(query.Query); innerQuery.Set("lang", "zh-cn"); innerQuery.Set("gacha_type", (int)type); @@ -47,11 +54,6 @@ public struct GachaLogQueryOptions EndId = endId; } - /// - /// 是否为国际服 - /// - public bool IsOversea { get; set; } - /// /// 结束Id /// 控制API返回的分页 diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs index 8071e744..693decbd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs @@ -189,30 +189,26 @@ internal class HomaClient .GetPlayerInfoAsync(userAndUid, token) .ConfigureAwait(false); - if (!playerInfoResponse.IsOk()) + if (playerInfoResponse.IsOk()) { - return null; + Response charactersResponse = await gameRecordClient + .GetCharactersAsync(userAndUid, playerInfoResponse.Data, token) + .ConfigureAwait(false); + + if (charactersResponse.IsOk()) + { + Response spiralAbyssResponse = await gameRecordClient + .GetSpiralAbyssAsync(userAndUid, SpiralAbyssSchedule.Current, token) + .ConfigureAwait(false); + + if (spiralAbyssResponse.IsOk()) + { + return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data); + } + } } - Response charactersResponse = await gameRecordClient - .GetCharactersAsync(userAndUid, playerInfoResponse.Data, token) - .ConfigureAwait(false); - - if (!charactersResponse.IsOk()) - { - return null; - } - - Response spiralAbyssResponse = await gameRecordClient - .GetSpiralAbyssAsync(userAndUid, SpiralAbyssSchedule.Current, token) - .ConfigureAwait(false); - - if (!spiralAbyssResponse.IsOk()) - { - return null; - } - - return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data); + return null; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs index 755cfd6b..c840469d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs @@ -32,16 +32,21 @@ internal class HomaClient2 /// 任务 public async Task UploadLogAsync(Exception exception) { - HutaoLog log = new() - { - Id = Core.CoreEnvironment.HutaoDeviceId, - Time = DateTimeOffset.Now.ToUnixTimeMilliseconds(), - Info = exception.ToString(), - }; + HutaoLog log = BuildFromException(exception); Response? a = await httpClient .TryCatchPostAsJsonAsync>(HutaoEndpoints.HutaoLogUpload, log) .ConfigureAwait(false); return a?.Data; } + + private static HutaoLog BuildFromException(Exception exception) + { + return new() + { + Id = Core.CoreEnvironment.HutaoDeviceId, + Time = DateTimeOffset.Now.ToUnixTimeMilliseconds(), + Info = exception.ToString(), + }; + } } \ No newline at end of file