mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
fix #439
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"pathSegment": {
|
||||
"add": {
|
||||
".*": [ ".cs" ]
|
||||
".*": [ ".cs", ".resx" ]
|
||||
}
|
||||
},
|
||||
"fileSuffixToExtension": {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 可枚举扩展
|
||||
/// </summary>
|
||||
public static class EnumerableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取选中的值或默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>选中的值或默认值</returns>
|
||||
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
|
||||
where TSource : ISelectable
|
||||
{
|
||||
return source.SingleOrDefault(i => i.IsSelected);
|
||||
}
|
||||
}
|
||||
@@ -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<IGachaLogUrlProvider> urlProviders;
|
||||
private readonly IEnumerable<IGachaLogQueryProvider> urlProviders;
|
||||
private readonly GachaInfoClient gachaInfoClient;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IGachaStatisticsFactory gachaStatisticsFactory;
|
||||
@@ -72,7 +73,7 @@ internal class GachaLogService : IGachaLogService
|
||||
/// <param name="messenger">消息器</param>
|
||||
public GachaLogService(
|
||||
AppDbContext appDbContext,
|
||||
IEnumerable<IGachaLogUrlProvider> urlProviders,
|
||||
IEnumerable<IGachaLogQueryProvider> urlProviders,
|
||||
GachaInfoClient gachaInfoClient,
|
||||
IMetadataService metadataService,
|
||||
IGachaStatisticsFactory gachaStatisticsFactory,
|
||||
@@ -172,7 +173,7 @@ internal class GachaLogService : IGachaLogService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGachaLogUrlProvider? GetGachaLogUrlProvider(RefreshOption option)
|
||||
public IGachaLogQueryProvider? GetGachaLogQueryProvider(RefreshOption option)
|
||||
{
|
||||
return option switch
|
||||
{
|
||||
@@ -207,7 +208,7 @@ internal class GachaLogService : IGachaLogService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> RefreshGachaLogAsync(string query, RefreshStrategy strategy, IProgress<FetchState> progress, CancellationToken token)
|
||||
public async Task<bool> RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress<FetchState> 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<ValueResult<bool, GachaArchive?>> FetchGachaLogsAsync(string query, bool isLazy, IProgress<FetchState> progress, CancellationToken token)
|
||||
private async Task<ValueResult<bool, GachaArchive?>> FetchGachaLogsAsync(GachaLogQuery query, bool isLazy, IProgress<FetchState> progress, CancellationToken token)
|
||||
{
|
||||
GachaArchive? archive = null;
|
||||
FetchState state = new();
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
/// <param name="option">刷新模式</param>
|
||||
/// <returns>祈愿日志Url提供器</returns>
|
||||
IGachaLogUrlProvider? GetGachaLogUrlProvider(RefreshOption option);
|
||||
IGachaLogQueryProvider? GetGachaLogQueryProvider(RefreshOption option);
|
||||
|
||||
/// <summary>
|
||||
/// 获得对应的祈愿统计
|
||||
@@ -69,7 +70,7 @@ internal interface IGachaLogService
|
||||
/// <param name="progress">进度</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>验证密钥是否可用</returns>
|
||||
Task<bool> RefreshGachaLogAsync(string query, RefreshStrategy strategy, IProgress<FetchState> progress, CancellationToken token);
|
||||
Task<bool> RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategy strategy, IProgress<FetchState> progress, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// 删除存档
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录query
|
||||
/// </summary>
|
||||
internal readonly struct GachaLogQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// query
|
||||
/// </summary>
|
||||
public readonly string Query;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为国际服
|
||||
/// </summary>
|
||||
public readonly bool IsOversea;
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
public readonly string Message;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的祈愿记录query
|
||||
/// </summary>
|
||||
/// <param name="query">query</param>
|
||||
/// <param name="isOversea">是否为国际服</param>
|
||||
public GachaLogQuery(string query, bool isOversea)
|
||||
{
|
||||
Query = query;
|
||||
IsOversea = isOversea;
|
||||
Message = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的失败的祈愿记录query
|
||||
/// </summary>
|
||||
/// <param name="message">失败原因</param>
|
||||
public GachaLogQuery(string message)
|
||||
{
|
||||
Message = message;
|
||||
Query = string.Empty;
|
||||
}
|
||||
|
||||
public static implicit operator GachaLogQuery(string message)
|
||||
{
|
||||
return new(message);
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,29 @@
|
||||
|
||||
using Snap.Hutao.View.Dialog;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog;
|
||||
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 手动输入方法
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient, typeof(IGachaLogUrlProvider))]
|
||||
internal class GachaLogUrlManualInputProvider : IGachaLogUrlProvider
|
||||
[Injection(InjectAs.Transient, typeof(IGachaLogQueryProvider))]
|
||||
internal class GachaLogUrlManualInputProvider : IGachaLogQueryProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name { get => nameof(GachaLogUrlManualInputProvider); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, string>> GetQueryAsync()
|
||||
public async Task<ValueResult<bool, GachaLogQuery>> GetQueryAsync()
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
ValueResult<bool, string> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 使用Stokn提供祈愿Url
|
||||
/// </summary>
|
||||
[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); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, string>> GetQueryAsync()
|
||||
public async Task<ValueResult<bool, GachaLogQuery>> 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
|
||||
{
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 浏览器缓存方法
|
||||
/// </summary>
|
||||
[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
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, string>> GetQueryAsync()
|
||||
public async Task<ValueResult<bool, GachaLogQuery>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,17 @@
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog;
|
||||
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录Url提供器
|
||||
/// </summary>
|
||||
internal interface IGachaLogUrlProvider : INamed
|
||||
internal interface IGachaLogQueryProvider : INamed
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取包含验证密钥的查询语句
|
||||
/// 查询语句可以仅包含?后的内容
|
||||
/// </summary>
|
||||
/// <returns>包含验证密钥的查询语句</returns>
|
||||
Task<ValueResult<bool, string>> GetQueryAsync();
|
||||
}
|
||||
Task<ValueResult<bool, GachaLogQuery>> GetQueryAsync();
|
||||
}
|
||||
@@ -166,13 +166,11 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BorderCardStyle}">
|
||||
<Grid
|
||||
MinHeight="48"
|
||||
Margin="0,8,0,0"
|
||||
Padding="8">
|
||||
<Grid MinHeight="48" Padding="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
|
||||
@@ -26,6 +26,19 @@ public abstract class ViewModel : ObservableObject, IViewModel
|
||||
/// <inheritdoc/>
|
||||
public bool IsViewDisposed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 保证 using scope 内的代码运行完成
|
||||
/// 防止 视图资源被回收
|
||||
/// </summary>
|
||||
/// <returns>解除执行限制</returns>
|
||||
protected async Task<IDisposable> EnterCriticalExecutionAsync()
|
||||
{
|
||||
ThrowIfViewDisposed();
|
||||
IDisposable disposable = await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false);
|
||||
ThrowIfViewDisposed();
|
||||
return disposable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当页面被释放后抛出异常
|
||||
/// </summary>
|
||||
|
||||
@@ -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<BindingAchievementGoal> sortedGoals;
|
||||
ObservableCollection<EntityAchievementArchive> archives;
|
||||
|
||||
ThrowIfViewDisposed();
|
||||
using (await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false))
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
ThrowIfViewDisposed();
|
||||
|
||||
List<MetadataAchievementGoal> 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<Model.Metadata.Achievement.Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
|
||||
List<MetadataAchievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
|
||||
List<BindingAchievement> combined;
|
||||
try
|
||||
{
|
||||
@@ -547,22 +547,22 @@ internal class AchievementViewModel : Abstraction.ViewModel, INavigationRecipien
|
||||
int count = 0;
|
||||
if (Achievements != null && AchievementGoals != null)
|
||||
{
|
||||
Dictionary<int, GoalAggregation> counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalAggregation(x));
|
||||
foreach (BindingAchievement achievement in Achievements.SourceCollection.OfType<BindingAchievement>())
|
||||
Dictionary<int, GoalStatistics> counter = AchievementGoals.ToDictionary(x => x.Id, x => new GoalStatistics(x));
|
||||
foreach (BindingAchievement achievement in Achievements.SourceCollection.Cast<BindingAchievement>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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<RefreshResult, Summary?> summaryResult;
|
||||
ThrowIfViewDisposed();
|
||||
using (await DisposeLock.EnterAsync(token).ConfigureAwait(false))
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
ThrowIfViewDisposed();
|
||||
ContentDialog dialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyFetch)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -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<DailyNoteEntry> temp = await dailyNoteService.GetDailyNoteEntriesAsync().ConfigureAwait(false);
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
DailyNoteEntries = temp;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
/// <summary>
|
||||
/// 祈愿记录请求配置
|
||||
/// </summary>
|
||||
public struct GachaLogQueryOptions
|
||||
internal struct GachaLogQueryOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 尺寸
|
||||
/// </summary>
|
||||
public const int Size = 20;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为国际服
|
||||
/// </summary>
|
||||
public readonly bool IsOversea;
|
||||
|
||||
/// <summary>
|
||||
/// Keys required:
|
||||
/// authkey_ver
|
||||
@@ -36,9 +42,10 @@ public struct GachaLogQueryOptions
|
||||
/// <param name="query">原始查询字符串</param>
|
||||
/// <param name="type">祈愿类型</param>
|
||||
/// <param name="endId">终止Id</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为国际服
|
||||
/// </summary>
|
||||
public bool IsOversea { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束Id
|
||||
/// 控制API返回的分页
|
||||
|
||||
@@ -189,30 +189,26 @@ internal class HomaClient
|
||||
.GetPlayerInfoAsync(userAndUid, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!playerInfoResponse.IsOk())
|
||||
if (playerInfoResponse.IsOk())
|
||||
{
|
||||
return null;
|
||||
Response<CharacterWrapper> charactersResponse = await gameRecordClient
|
||||
.GetCharactersAsync(userAndUid, playerInfoResponse.Data, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (charactersResponse.IsOk())
|
||||
{
|
||||
Response<SpiralAbyss> spiralAbyssResponse = await gameRecordClient
|
||||
.GetSpiralAbyssAsync(userAndUid, SpiralAbyssSchedule.Current, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (spiralAbyssResponse.IsOk())
|
||||
{
|
||||
return new(userAndUid.Uid.Value, charactersResponse.Data.Avatars, spiralAbyssResponse.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Response<CharacterWrapper> charactersResponse = await gameRecordClient
|
||||
.GetCharactersAsync(userAndUid, playerInfoResponse.Data, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!charactersResponse.IsOk())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Response<SpiralAbyss> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -32,16 +32,21 @@ internal class HomaClient2
|
||||
/// <returns>任务</returns>
|
||||
public async Task<string?> UploadLogAsync(Exception exception)
|
||||
{
|
||||
HutaoLog log = new()
|
||||
{
|
||||
Id = Core.CoreEnvironment.HutaoDeviceId,
|
||||
Time = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
|
||||
Info = exception.ToString(),
|
||||
};
|
||||
HutaoLog log = BuildFromException(exception);
|
||||
|
||||
Response<string>? a = await httpClient
|
||||
.TryCatchPostAsJsonAsync<HutaoLog, Response<string>>(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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user