mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Achievement functional
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.3" />
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.VisualStudio.Threading;
|
|
||||||
using Microsoft.Windows.AppLifecycle;
|
using Microsoft.Windows.AppLifecycle;
|
||||||
|
using Snap.Hutao.Core.LifeCycle;
|
||||||
using Snap.Hutao.Core.Logging;
|
using Snap.Hutao.Core.Logging;
|
||||||
using Snap.Hutao.Extension;
|
using Snap.Hutao.Extension;
|
||||||
using Snap.Hutao.Service.Abstraction;
|
using Snap.Hutao.Service.Abstraction;
|
||||||
@@ -23,6 +23,7 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
private static Window? window;
|
private static Window? window;
|
||||||
private readonly ILogger<App> logger;
|
private readonly ILogger<App> logger;
|
||||||
|
private readonly ExceptionRecorder exceptionRecorder;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the singleton application object.
|
/// Initializes the singleton application object.
|
||||||
@@ -37,9 +38,7 @@ public partial class App : Application
|
|||||||
// so we can use Ioc here.
|
// so we can use Ioc here.
|
||||||
logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
||||||
|
|
||||||
UnhandledException += AppUnhandledException;
|
exceptionRecorder = new(this, logger);
|
||||||
DebugSettings.BindingFailed += XamlBindingFailed;
|
|
||||||
TaskScheduler.UnobservedTaskException += TaskSchedulerUnobservedTaskException;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -74,13 +73,11 @@ public partial class App : Application
|
|||||||
|
|
||||||
if (firstInstance.IsCurrent)
|
if (firstInstance.IsCurrent)
|
||||||
{
|
{
|
||||||
|
OnActivated(firstInstance, activatedEventArgs);
|
||||||
firstInstance.Activated += OnActivated;
|
firstInstance.Activated += OnActivated;
|
||||||
Window = Ioc.Default.GetRequiredService<MainWindow>();
|
|
||||||
|
|
||||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", CacheFolder.Path);
|
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", CacheFolder.Path);
|
||||||
|
|
||||||
OnActivated(firstInstance, activatedEventArgs);
|
|
||||||
|
|
||||||
Ioc.Default
|
Ioc.Default
|
||||||
.GetRequiredService<IMetadataService>()
|
.GetRequiredService<IMetadataService>()
|
||||||
.ImplictAs<IMetadataInitializer>()?
|
.ImplictAs<IMetadataInitializer>()?
|
||||||
@@ -106,10 +103,10 @@ public partial class App : Application
|
|||||||
.AddMemoryCache()
|
.AddMemoryCache()
|
||||||
|
|
||||||
// Hutao extensions
|
// Hutao extensions
|
||||||
|
.AddJsonSerializerOptions()
|
||||||
|
.AddDatebase()
|
||||||
.AddInjections()
|
.AddInjections()
|
||||||
.AddHttpClients()
|
.AddHttpClients()
|
||||||
.AddDatebase()
|
|
||||||
.AddJsonSerializerOptions()
|
|
||||||
|
|
||||||
// Discrete services
|
// Discrete services
|
||||||
.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
|
.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
|
||||||
@@ -123,9 +120,10 @@ public partial class App : Application
|
|||||||
[SuppressMessage("", "VSTHRD100")]
|
[SuppressMessage("", "VSTHRD100")]
|
||||||
private async void OnActivated(object? sender, AppActivationArguments args)
|
private async void OnActivated(object? sender, AppActivationArguments args)
|
||||||
{
|
{
|
||||||
|
Window = Ioc.Default.GetRequiredService<MainWindow>();
|
||||||
|
|
||||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||||
await infoBarService.WaitInitializationAsync();
|
await infoBarService.WaitInitializationAsync();
|
||||||
infoBarService.Information("OnActivated");
|
|
||||||
|
|
||||||
if (args.Kind == ExtendedActivationKind.Protocol)
|
if (args.Kind == ExtendedActivationKind.Protocol)
|
||||||
{
|
{
|
||||||
@@ -135,19 +133,4 @@ public partial class App : Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
|
||||||
{
|
|
||||||
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常: [HResult:{code}]", e.Exception.HResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void XamlBindingFailed(object sender, BindingFailedEventArgs e)
|
|
||||||
{
|
|
||||||
logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TaskSchedulerUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
|
||||||
{
|
|
||||||
logger.LogCritical(EventIds.UnobservedTaskException, "异步任务执行异常: {message}", e.Exception);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,25 @@ public class AppDbContext : DbContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置项
|
/// 设置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DbSet<SettingEntry> Settings { get; set; } = default!;
|
public DbSet<SettingEntry> Settings { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户表
|
/// 用户
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DbSet<User> Users { get; set; } = default!;
|
public DbSet<User> Users { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成就
|
||||||
|
/// </summary>
|
||||||
|
public DbSet<Achievement> Achievements { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成就存档
|
||||||
|
/// </summary>
|
||||||
|
public DbSet<AchievementArchive> AchievementArchives { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个临时的应用程序数据库上下文
|
/// 构造一个临时的应用程序数据库上下文
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using CommunityToolkit.WinUI.UI.Behaviors;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Snap.Hutao.Core;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Control.Behavior;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在元素卸载完成后执行命令的行为
|
||||||
|
/// </summary>
|
||||||
|
internal class InvokeCommandOnUnloadedBehavior : BehaviorBase<UIElement>
|
||||||
|
{
|
||||||
|
private static readonly DependencyProperty CommandProperty = Property<InvokeCommandOnUnloadedBehavior>.Depend<ICommand>(nameof(Command));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 待执行的命令
|
||||||
|
/// </summary>
|
||||||
|
public ICommand Command
|
||||||
|
{
|
||||||
|
get => (ICommand)GetValue(CommandProperty);
|
||||||
|
set => SetValue(CommandProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void OnDetaching()
|
||||||
|
{
|
||||||
|
if (Command != null && Command.CanExecute(null))
|
||||||
|
{
|
||||||
|
Command.Execute(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnDetaching();
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Snap.Hutao/Snap.Hutao/Control/ContentDialog2.cs
Normal file
26
src/Snap.Hutao/Snap.Hutao/Control/ContentDialog2.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// 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() });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Snap.Hutao.Core.Logging;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.LifeCycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异常记录器
|
||||||
|
/// </summary>
|
||||||
|
internal class ExceptionRecorder
|
||||||
|
{
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的异常记录器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="application">应用程序</param>
|
||||||
|
/// <param name="logger">日志器</param>
|
||||||
|
public ExceptionRecorder(Application application, ILogger logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
|
||||||
|
application.UnhandledException += OnAppUnhandledException;
|
||||||
|
application.DebugSettings.BindingFailed += OnXamlBindingFailed;
|
||||||
|
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当应用程序未经处理的异常引发时调用
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">实例</param>
|
||||||
|
/// <param name="e">事件参数</param>
|
||||||
|
public void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常: [HResult:{code}]", e.Exception.HResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Xaml 绑定失败时触发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">实例</param>
|
||||||
|
/// <param name="e">事件参数</param>
|
||||||
|
public void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
|
||||||
|
{
|
||||||
|
logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步命令异常时触发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">实例</param>
|
||||||
|
/// <param name="e">事件参数</param>
|
||||||
|
public void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
logger.LogCritical(EventIds.UnobservedTaskException, "异步任务执行异常: {message}", e.Exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,11 @@ internal static class EventIds
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly EventId FileCaching = 100120;
|
public static readonly EventId FileCaching = 100120;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件缓存
|
||||||
|
/// </summary>
|
||||||
|
public static readonly EventId Achievement = 100130;
|
||||||
|
|
||||||
// 杂项
|
// 杂项
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Extension;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DateTimeOffset"/> 扩展
|
||||||
|
/// </summary>
|
||||||
|
public static class DateTimeOffsetExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the current <see cref="DateTimeOffset"/> to a <see cref="DateTimeOffset"/> that represents the local time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dateTimeOffset">时间偏移</param>
|
||||||
|
/// <param name="keepTicks">保留主时间部分</param>
|
||||||
|
/// <returns>A <see cref="DateTimeOffset"/> that represents the local time.</returns>
|
||||||
|
public static DateTimeOffset ToLocalTime(this DateTimeOffset dateTimeOffset, bool keepTicks)
|
||||||
|
{
|
||||||
|
if (keepTicks)
|
||||||
|
{
|
||||||
|
dateTimeOffset += TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now).Negate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateTimeOffset.ToLocalTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Snap.Hutao.Context.Database;
|
using Snap.Hutao.Context.Database;
|
||||||
using Snap.Hutao.Core.Windowing;
|
using Snap.Hutao.Core.Windowing;
|
||||||
|
using Snap.Hutao.Message;
|
||||||
|
|
||||||
namespace Snap.Hutao;
|
namespace Snap.Hutao;
|
||||||
|
|
||||||
@@ -15,6 +17,7 @@ public sealed partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
private readonly AppDbContext appDbContext;
|
private readonly AppDbContext appDbContext;
|
||||||
private readonly WindowManager windowManager;
|
private readonly WindowManager windowManager;
|
||||||
|
private readonly IMessenger messenger;
|
||||||
|
|
||||||
private readonly TaskCompletionSource initializaionCompletionSource = new();
|
private readonly TaskCompletionSource initializaionCompletionSource = new();
|
||||||
|
|
||||||
@@ -22,20 +25,22 @@ public sealed partial class MainWindow : Window
|
|||||||
/// 构造一个新的主窗体
|
/// 构造一个新的主窗体
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appDbContext">数据库上下文</param>
|
/// <param name="appDbContext">数据库上下文</param>
|
||||||
/// <param name="logger">日志器</param>
|
/// <param name="messenger">消息器</param>
|
||||||
public MainWindow(AppDbContext appDbContext, ILogger<MainWindow> logger)
|
public MainWindow(AppDbContext appDbContext, IMessenger messenger)
|
||||||
{
|
{
|
||||||
this.appDbContext = appDbContext;
|
this.appDbContext = appDbContext;
|
||||||
|
this.messenger = messenger;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
windowManager = new WindowManager(this, TitleBarView.DragableArea);
|
windowManager = new WindowManager(this, TitleBarView.DragableArea);
|
||||||
|
|
||||||
initializaionCompletionSource.TrySetResult();
|
initializaionCompletionSource.TrySetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void MainWindowClosed(object sender, WindowEventArgs args)
|
private void MainWindowClosed(object sender, WindowEventArgs args)
|
||||||
{
|
{
|
||||||
|
messenger.Send(new MainWindowClosedMessage());
|
||||||
|
|
||||||
windowManager?.Dispose();
|
windowManager?.Dispose();
|
||||||
|
|
||||||
// save userdata datebase
|
// save userdata datebase
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Model.Entity;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Message;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成就存档切换消息
|
||||||
|
/// </summary>
|
||||||
|
internal class AchievementArchiveChangedMessage : ValueChangedMessage<AchievementArchive>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的用户切换消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldArchive">老用户</param>
|
||||||
|
/// <param name="newArchive">新用户</param>
|
||||||
|
public AchievementArchiveChangedMessage(AchievementArchive? oldArchive, AchievementArchive? newArchive)
|
||||||
|
: base(oldArchive, newArchive)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Snap.Hutao/Snap.Hutao/Message/MainWindowClosedMessage.cs
Normal file
13
src/Snap.Hutao/Snap.Hutao/Message/MainWindowClosedMessage.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Model.Binding;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Message;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主窗体关闭消息
|
||||||
|
/// </summary>
|
||||||
|
internal class MainWindowClosedMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
22
src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs
Normal file
22
src/Snap.Hutao/Snap.Hutao/Message/UserChangedMessage.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Model.Binding;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Message;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户切换消息
|
||||||
|
/// </summary>
|
||||||
|
internal class UserChangedMessage : ValueChangedMessage<User>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的用户切换消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldUser">老用户</param>
|
||||||
|
/// <param name="newUser">新用户</param>
|
||||||
|
public UserChangedMessage(User? oldUser, User? newUser)
|
||||||
|
: base(oldUser, newUser)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs
Normal file
33
src/Snap.Hutao/Snap.Hutao/Message/ValueChangedMessage.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Message;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 值变化消息
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue">值的类型</typeparam>
|
||||||
|
internal abstract class ValueChangedMessage<TValue>
|
||||||
|
where TValue : class
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的值变化消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldValue">旧值</param>
|
||||||
|
/// <param name="newValue">新值</param>
|
||||||
|
public ValueChangedMessage(TValue? oldValue, TValue? newValue)
|
||||||
|
{
|
||||||
|
OldValue = oldValue;
|
||||||
|
NewValue = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 旧的值
|
||||||
|
/// </summary>
|
||||||
|
public TValue? OldValue { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新的值
|
||||||
|
/// </summary>
|
||||||
|
public TValue? NewValue { get; private set; }
|
||||||
|
}
|
||||||
90
src/Snap.Hutao/Snap.Hutao/Migrations/20220813040006_AddAchievement.Designer.cs
generated
Normal file
90
src/Snap.Hutao/Snap.Hutao/Migrations/20220813040006_AddAchievement.Designer.cs
generated
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Snap.Hutao.Context.Database;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20220813040006_AddAchievement")]
|
||||||
|
partial class AddAchievement
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "6.0.8");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("InnerId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Current")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("Time")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("InnerId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("achievements");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Key");
|
||||||
|
|
||||||
|
b.ToTable("settings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("InnerId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Cookie")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsSelected")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("InnerId");
|
||||||
|
|
||||||
|
b.ToTable("users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddAchievement : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "achievements",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Current = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Time = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_achievements", x => x.InnerId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_achievements_users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "users",
|
||||||
|
principalColumn: "InnerId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_achievements_UserId",
|
||||||
|
table: "achievements",
|
||||||
|
column: "UserId");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "achievements");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
111
src/Snap.Hutao/Snap.Hutao/Migrations/20220815133601_AddAchievementArchive.Designer.cs
generated
Normal file
111
src/Snap.Hutao/Snap.Hutao/Migrations/20220815133601_AddAchievementArchive.Designer.cs
generated
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Snap.Hutao.Context.Database;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20220815133601_AddAchievementArchive")]
|
||||||
|
partial class AddAchievementArchive
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "6.0.8");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("InnerId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("ArchiveId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Current")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("Time")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("InnerId");
|
||||||
|
|
||||||
|
b.HasIndex("ArchiveId");
|
||||||
|
|
||||||
|
b.ToTable("achievements");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("InnerId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsSelected")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("InnerId");
|
||||||
|
|
||||||
|
b.ToTable("achievement_archives");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Key");
|
||||||
|
|
||||||
|
b.ToTable("settings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("InnerId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Cookie")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsSelected")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("InnerId");
|
||||||
|
|
||||||
|
b.ToTable("users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ArchiveId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Archive");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddAchievementArchive : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_achievements_users_UserId",
|
||||||
|
table: "achievements");
|
||||||
|
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "UserId",
|
||||||
|
table: "achievements",
|
||||||
|
newName: "ArchiveId");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_achievements_UserId",
|
||||||
|
table: "achievements",
|
||||||
|
newName: "IX_achievements_ArchiveId");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "Status",
|
||||||
|
table: "achievements",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "achievement_archives",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
IsSelected = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_achievement_archives", x => x.InnerId);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_achievements_achievement_archives_ArchiveId",
|
||||||
|
table: "achievements",
|
||||||
|
column: "ArchiveId",
|
||||||
|
principalTable: "achievement_archives",
|
||||||
|
principalColumn: "InnerId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_achievements_achievement_archives_ArchiveId",
|
||||||
|
table: "achievements");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "achievement_archives");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Status",
|
||||||
|
table: "achievements");
|
||||||
|
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "ArchiveId",
|
||||||
|
table: "achievements",
|
||||||
|
newName: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_achievements_ArchiveId",
|
||||||
|
table: "achievements",
|
||||||
|
newName: "IX_achievements_UserId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_achievements_users_UserId",
|
||||||
|
table: "achievements",
|
||||||
|
column: "UserId",
|
||||||
|
principalTable: "users",
|
||||||
|
principalColumn: "InnerId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using Snap.Hutao.Context.Database;
|
using Snap.Hutao.Context.Database;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
@@ -13,7 +15,53 @@ namespace Snap.Hutao.Migrations
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.7");
|
modelBuilder.HasAnnotation("ProductVersion", "6.0.8");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("InnerId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("ArchiveId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Current")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("Time")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("InnerId");
|
||||||
|
|
||||||
|
b.HasIndex("ArchiveId");
|
||||||
|
|
||||||
|
b.ToTable("achievements");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("InnerId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsSelected")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("InnerId");
|
||||||
|
|
||||||
|
b.ToTable("achievement_archives");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||||
{
|
{
|
||||||
@@ -44,6 +92,17 @@ namespace Snap.Hutao.Migrations
|
|||||||
|
|
||||||
b.ToTable("users");
|
b.ToTable("users");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ArchiveId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Archive");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
src/Snap.Hutao/Snap.Hutao/Model/Binding/Achievement.cs
Normal file
63
src/Snap.Hutao/Snap.Hutao/Model/Binding/Achievement.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.Binding;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于视图绑定的成就
|
||||||
|
/// </summary>
|
||||||
|
public class Achievement : Observable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 满进度占位符
|
||||||
|
/// </summary>
|
||||||
|
public const int FullProgressPlaceholder = int.MaxValue;
|
||||||
|
|
||||||
|
private readonly Metadata.Achievement.Achievement inner;
|
||||||
|
private readonly Entity.Achievement entity;
|
||||||
|
|
||||||
|
private bool isChecked;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的成就
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inner">元数据部分</param>
|
||||||
|
/// <param name="entity">实体部分</param>
|
||||||
|
public Achievement(Metadata.Achievement.Achievement inner, Entity.Achievement entity)
|
||||||
|
{
|
||||||
|
this.inner = inner;
|
||||||
|
this.entity = entity;
|
||||||
|
|
||||||
|
// Property should only be set when is user checking.
|
||||||
|
isChecked = (int)entity.Status >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实体
|
||||||
|
/// </summary>
|
||||||
|
public Entity.Achievement Entity { get => entity; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 元数据
|
||||||
|
/// </summary>
|
||||||
|
public Metadata.Achievement.Achievement Inner { get => inner; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否选中
|
||||||
|
/// </summary>
|
||||||
|
public bool IsChecked
|
||||||
|
{
|
||||||
|
get => isChecked;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Set(ref isChecked, value);
|
||||||
|
|
||||||
|
// Only update state when checked
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
Entity.Status = Intrinsic.AchievementInfoStatus.ACHIEVEMENT_POINT_TAKEN;
|
||||||
|
Entity.Time = DateTimeOffset.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
118
src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs
Normal file
118
src/Snap.Hutao/Snap.Hutao/Model/Entity/Achievement.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Extension;
|
||||||
|
using Snap.Hutao.Model.InterChange.Achievement;
|
||||||
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成就
|
||||||
|
/// </summary>
|
||||||
|
[Table("achievements")]
|
||||||
|
public class Achievement : IEquatable<Achievement>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 内部Id
|
||||||
|
/// </summary>
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public Guid InnerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 完成成就的用户
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(ArchiveId))]
|
||||||
|
public AchievementArchive Archive { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 完成成就的用户Id
|
||||||
|
/// </summary>
|
||||||
|
public Guid ArchiveId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Id
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前进度
|
||||||
|
/// </summary>
|
||||||
|
public int Current { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 完成时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset Time { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public AchievementInfoStatus Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建一个新的成就
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">对应的用户id</param>
|
||||||
|
/// <param name="id">成就Id</param>
|
||||||
|
/// <returns>新创建的成就</returns>
|
||||||
|
public static Achievement Create(Guid userId, int id)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ArchiveId = userId,
|
||||||
|
Id = id,
|
||||||
|
Current = 0,
|
||||||
|
Time = DateTimeOffset.MinValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建一个新的成就
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">对应的用户id</param>
|
||||||
|
/// <param name="uiaf">uiaf项</param>
|
||||||
|
/// <returns>新创建的成就</returns>
|
||||||
|
public static Achievement Create(Guid userId, UIAFItem uiaf)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ArchiveId = userId,
|
||||||
|
Id = uiaf.Id,
|
||||||
|
Current = uiaf.Current,
|
||||||
|
Time = DateTimeOffset.FromUnixTimeSeconds(uiaf.Timestamp).ToLocalTime(true),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(Achievement? other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ArchiveId == other.ArchiveId
|
||||||
|
&& Id == other.Id
|
||||||
|
&& Current == other.Current
|
||||||
|
&& Status == other.Status
|
||||||
|
&& Time == other.Time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return Equals(obj as Achievement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(ArchiveId, Id, Current, Status, Time);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/Snap.Hutao/Snap.Hutao/Model/Entity/AchievementArchive.cs
Normal file
41
src/Snap.Hutao/Snap.Hutao/Model/Entity/AchievementArchive.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成就存档
|
||||||
|
/// </summary>
|
||||||
|
[Table("achievement_archives")]
|
||||||
|
public class AchievementArchive
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 内部Id
|
||||||
|
/// </summary>
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public Guid InnerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否选中
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSelected { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建一个新的存档
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">名称</param>
|
||||||
|
/// <returns>新存档</returns>
|
||||||
|
public static AchievementArchive Create(string name)
|
||||||
|
{
|
||||||
|
return new() { Name = name };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,4 +28,14 @@ public class User
|
|||||||
/// 用户的Cookie
|
/// 用户的Cookie
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Cookie { get; set; }
|
public string? Cookie { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建一个新的用户
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cookie">cookie</param>
|
||||||
|
/// <returns>新创建的用户</returns>
|
||||||
|
public static User Create(string cookie)
|
||||||
|
{
|
||||||
|
return new() { Cookie = cookie };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统一可交换成就格式
|
||||||
|
/// </summary>
|
||||||
|
public class UIAF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 信息
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||||
|
public UIAFInfo Info { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 列表
|
||||||
|
/// </summary>
|
||||||
|
public List<UIAFItem> List { get; set; } = default!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UIAF格式的信息
|
||||||
|
/// </summary>
|
||||||
|
public class UIAFInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 导出的 App 名称
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("export_app")]
|
||||||
|
public string ExportApp { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出的时间戳
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("export_timestamp")]
|
||||||
|
public long? ExportTimestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset ExportDateTime
|
||||||
|
{
|
||||||
|
get => DateTimeOffset.FromUnixTimeSeconds(ExportTimestamp ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出的 App 版本
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("export_app_version")]
|
||||||
|
public string? ExportAppVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用的UIAF版本
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("uiaf_version")]
|
||||||
|
public string? UIAFVersion { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.InterChange.Achievement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UIAF 项
|
||||||
|
/// </summary>
|
||||||
|
public class UIAFItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 成就Id
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 完成时间
|
||||||
|
/// </summary>
|
||||||
|
public long Timestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前值
|
||||||
|
/// 对于progress为1的项,该属性始终为0
|
||||||
|
/// </summary>
|
||||||
|
public int Current { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 完成状态
|
||||||
|
/// </summary>
|
||||||
|
public AchievementInfoStatus Status { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.Intrinsic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成就信息状态
|
||||||
|
/// https://github.com/Grasscutters/Grasscutter/blob/development/proto/AchievementInfo.proto
|
||||||
|
/// </summary>
|
||||||
|
public enum AchievementInfoStatus
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 非法值
|
||||||
|
/// </summary>
|
||||||
|
ACHIEVEMENT_INVALID = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 未完成
|
||||||
|
/// </summary>
|
||||||
|
ACHIEVEMENT_UNFINISHED = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已完成
|
||||||
|
/// </summary>
|
||||||
|
ACHIEVEMENT_FINISHED = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 奖励已领取
|
||||||
|
/// </summary>
|
||||||
|
ACHIEVEMENT_POINT_TAKEN = 3,
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Model;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 简单的实现了 <see cref="INotifyPropertyChanged"/> 接口
|
/// 简单的实现了 <see cref="INotifyPropertyChanged"/> 接口
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Observable : INotifyPropertyChanged
|
public abstract class Observable : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
@@ -20,15 +20,17 @@ public class Observable : INotifyPropertyChanged
|
|||||||
/// <param name="storage">现有值</param>
|
/// <param name="storage">现有值</param>
|
||||||
/// <param name="value">新的值</param>
|
/// <param name="value">新的值</param>
|
||||||
/// <param name="propertyName">属性名称</param>
|
/// <param name="propertyName">属性名称</param>
|
||||||
protected void Set<T>([NotNullIfNotNull("value")] ref T storage, T value, [CallerMemberName] string propertyName = default!)
|
/// <returns>项是否更新</returns>
|
||||||
|
protected bool Set<T>([NotNullIfNotNull("value")] ref T storage, T value, [CallerMemberName] string propertyName = default!)
|
||||||
{
|
{
|
||||||
if (Equals(storage, value))
|
if (Equals(storage, value))
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
storage = value;
|
storage = value;
|
||||||
OnPropertyChanged(propertyName);
|
OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||||
Publisher="CN=DGP Studio"
|
Publisher="CN=DGP Studio"
|
||||||
Version="1.0.26.0" />
|
Version="1.0.28.0" />
|
||||||
|
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>胡桃</DisplayName>
|
<DisplayName>胡桃</DisplayName>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public static class Program
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
private static void Main(string[] args)
|
private static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
_ = args;
|
||||||
XamlCheckProcessRequirements();
|
XamlCheckProcessRequirements();
|
||||||
ComWrappersSupport.InitializeComWrappers();
|
ComWrappersSupport.InitializeComWrappers();
|
||||||
|
|
||||||
|
|||||||
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_201.png
Normal file
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_201.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -1,6 +1,19 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Snap.Hutao.Context.Database;
|
||||||
|
using Snap.Hutao.Core.Diagnostics;
|
||||||
|
using Snap.Hutao.Core.Logging;
|
||||||
|
using Snap.Hutao.Model.InterChange.Achievement;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement;
|
||||||
|
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
|
||||||
|
using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
|
||||||
|
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Achievement;
|
namespace Snap.Hutao.Service.Achievement;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -9,5 +22,352 @@ namespace Snap.Hutao.Service.Achievement;
|
|||||||
[Injection(InjectAs.Transient, typeof(IAchievementService))]
|
[Injection(InjectAs.Transient, typeof(IAchievementService))]
|
||||||
internal class AchievementService : IAchievementService
|
internal class AchievementService : IAchievementService
|
||||||
{
|
{
|
||||||
|
private readonly AppDbContext appDbContext;
|
||||||
|
private readonly ILogger<AchievementService> logger;
|
||||||
|
private readonly IMessenger messenger;
|
||||||
|
|
||||||
|
private EntityArchive? currentArchive;
|
||||||
|
private ObservableCollection<EntityArchive>? archiveCollection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的成就服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appDbContext">数据库上下文</param>
|
||||||
|
/// <param name="logger">日志器</param>
|
||||||
|
/// <param name="messenger">消息器</param>
|
||||||
|
public AchievementService(AppDbContext appDbContext, ILogger<AchievementService> logger, IMessenger messenger)
|
||||||
|
{
|
||||||
|
this.appDbContext = appDbContext;
|
||||||
|
this.logger = logger;
|
||||||
|
this.messenger = messenger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public EntityArchive? CurrentArchive
|
||||||
|
{
|
||||||
|
get => currentArchive;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (currentArchive == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only update when not processing a deletion
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
if (currentArchive != null)
|
||||||
|
{
|
||||||
|
currentArchive.IsSelected = false;
|
||||||
|
appDbContext.AchievementArchives.Update(currentArchive);
|
||||||
|
appDbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message.AchievementArchiveChangedMessage message = new(currentArchive, value);
|
||||||
|
|
||||||
|
currentArchive = value;
|
||||||
|
|
||||||
|
if (currentArchive != null)
|
||||||
|
{
|
||||||
|
currentArchive.IsSelected = true;
|
||||||
|
appDbContext.AchievementArchives.Update(currentArchive);
|
||||||
|
appDbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
messenger.Send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ObservableCollection<EntityArchive> GetArchiveCollection()
|
||||||
|
{
|
||||||
|
return archiveCollection ??= new(appDbContext.AchievementArchives.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public List<BindingAchievement> GetAchievements(EntityArchive archive, IList<MetadataAchievement> metadata)
|
||||||
|
{
|
||||||
|
Guid archiveId = archive.InnerId;
|
||||||
|
List<EntityAchievement> entities = appDbContext.Achievements
|
||||||
|
.Where(a => a.ArchiveId == archiveId)
|
||||||
|
|
||||||
|
// Important! Prevent multiple sql command for SingleOrDefault below.
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
List<BindingAchievement> results = new();
|
||||||
|
foreach (MetadataAchievement meta in metadata)
|
||||||
|
{
|
||||||
|
EntityAchievement entity = entities.SingleOrDefault(e => e.Id == meta.Id)
|
||||||
|
?? EntityAchievement.Create(archiveId, meta.Id);
|
||||||
|
|
||||||
|
results.Add(new(meta, entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ImportResult ImportFromUIAF(EntityArchive archive, List<UIAFItem> list, ImportOption option)
|
||||||
|
{
|
||||||
|
Guid archiveId = archive.InnerId;
|
||||||
|
|
||||||
|
switch (option)
|
||||||
|
{
|
||||||
|
case ImportOption.AggressiveMerge:
|
||||||
|
{
|
||||||
|
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
|
||||||
|
return MergeAchievements(archiveId, orederedUIAF, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
case ImportOption.LazyMerge:
|
||||||
|
{
|
||||||
|
IOrderedEnumerable<UIAFItem> orederedUIAF = list.OrderBy(a => a.Id);
|
||||||
|
return MergeAchievements(archiveId, orederedUIAF, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
case ImportOption.Overwrite:
|
||||||
|
{
|
||||||
|
IEnumerable<EntityAchievement> newData = list
|
||||||
|
.Select(uiaf => EntityAchievement.Create(archiveId, uiaf))
|
||||||
|
.OrderBy(a => a.Id);
|
||||||
|
return OverwriteAchievements(archiveId, newData);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Must.NeverHappen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Task RemoveArchiveAsync(EntityArchive archive)
|
||||||
|
{
|
||||||
|
Must.NotNull(archiveCollection!);
|
||||||
|
|
||||||
|
// Sync cache
|
||||||
|
archiveCollection.Remove(archive);
|
||||||
|
|
||||||
|
// Sync database
|
||||||
|
appDbContext.AchievementArchives.Remove(archive);
|
||||||
|
return appDbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void SaveAchievements(EntityArchive archive, IList<BindingAchievement> achievements)
|
||||||
|
{
|
||||||
|
string name = archive.Name;
|
||||||
|
logger.LogInformation(EventIds.Achievement, "Begin saving achievements for [{name}]", name);
|
||||||
|
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
|
||||||
|
|
||||||
|
IEnumerable<EntityAchievement> newData = achievements
|
||||||
|
.Where(a => a.IsChecked)
|
||||||
|
.Select(a => a.Entity)
|
||||||
|
.OrderBy(a => a.Id);
|
||||||
|
ImportResult result = OverwriteAchievements(archive.InnerId, newData);
|
||||||
|
|
||||||
|
double time = stopwatch.GetElapsedTime().TotalMilliseconds;
|
||||||
|
logger.LogInformation(EventIds.Achievement, "{add} added, {update} updated, {remove} removed", result.Add, result.Update, result.Remove);
|
||||||
|
logger.LogInformation(EventIds.Achievement, "Save achievements for [{name}] completed in {time}ms", name, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(newArchive.Name))
|
||||||
|
{
|
||||||
|
return ArchiveAddResult.InvalidName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Must.NotNull(archiveCollection!);
|
||||||
|
|
||||||
|
// 查找是否有相同的名称
|
||||||
|
if (archiveCollection.SingleOrDefault(a => a.Name == newArchive.Name) is EntityArchive userWithSameUid)
|
||||||
|
{
|
||||||
|
return ArchiveAddResult.AlreadyExists;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Sync cache
|
||||||
|
archiveCollection.Add(newArchive);
|
||||||
|
|
||||||
|
// Sync database
|
||||||
|
appDbContext.AchievementArchives.Add(newArchive);
|
||||||
|
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
return ArchiveAddResult.Added;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportResult MergeAchievements(Guid archiveId, IOrderedEnumerable<UIAFItem> orederedUIAF, bool aggressive)
|
||||||
|
{
|
||||||
|
IOrderedQueryable<EntityAchievement> oldData = appDbContext.Achievements
|
||||||
|
.Where(a => a.ArchiveId == archiveId)
|
||||||
|
.OrderBy(a => a.Id);
|
||||||
|
|
||||||
|
int add = 0;
|
||||||
|
int update = 0;
|
||||||
|
int remove = 0;
|
||||||
|
|
||||||
|
using (IEnumerator<EntityAchievement> entityEnumerator = oldData.GetEnumerator())
|
||||||
|
{
|
||||||
|
using (IEnumerator<UIAFItem> uiafEnumerator = orederedUIAF.GetEnumerator())
|
||||||
|
{
|
||||||
|
bool moveEntity = true;
|
||||||
|
bool moveUIAF = true;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
bool moveEntityResult = moveEntity && entityEnumerator.MoveNext();
|
||||||
|
bool moveUIAFResult = moveUIAF && uiafEnumerator.MoveNext();
|
||||||
|
|
||||||
|
if (!(moveEntityResult || moveUIAFResult))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EntityAchievement? entity = entityEnumerator.Current;
|
||||||
|
UIAFItem? uiaf = uiafEnumerator.Current;
|
||||||
|
|
||||||
|
if (entity == null && uiaf != null)
|
||||||
|
{
|
||||||
|
AddEntity(EntityAchievement.Create(archiveId, uiaf));
|
||||||
|
add++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (entity != null && uiaf == null)
|
||||||
|
{
|
||||||
|
// skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity!.Id < uiaf!.Id)
|
||||||
|
{
|
||||||
|
moveEntity = true;
|
||||||
|
moveUIAF = false;
|
||||||
|
}
|
||||||
|
else if (entity.Id == uiaf.Id)
|
||||||
|
{
|
||||||
|
moveEntity = true;
|
||||||
|
moveUIAF = true;
|
||||||
|
|
||||||
|
if (aggressive)
|
||||||
|
{
|
||||||
|
RemoveEntity(entity);
|
||||||
|
AddEntity(EntityAchievement.Create(archiveId, uiaf));
|
||||||
|
update++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// entity.Id > uiaf.Id
|
||||||
|
moveEntity = false;
|
||||||
|
moveUIAF = true;
|
||||||
|
|
||||||
|
AddEntity(EntityAchievement.Create(archiveId, uiaf));
|
||||||
|
add++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(add, update, remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportResult OverwriteAchievements(Guid archiveId, IEnumerable<EntityAchievement> newData)
|
||||||
|
{
|
||||||
|
IQueryable<EntityAchievement> oldData = appDbContext.Achievements
|
||||||
|
.Where(a => a.ArchiveId == archiveId)
|
||||||
|
.OrderBy(a => a.Id);
|
||||||
|
|
||||||
|
int add = 0;
|
||||||
|
int update = 0;
|
||||||
|
int remove = 0;
|
||||||
|
|
||||||
|
using (IEnumerator<EntityAchievement> oldDataEnumerator = oldData.GetEnumerator())
|
||||||
|
{
|
||||||
|
using (IEnumerator<EntityAchievement> newDataEnumerator = newData.GetEnumerator())
|
||||||
|
{
|
||||||
|
bool moveOld = true;
|
||||||
|
bool moveNew = true;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
bool moveOldResult = moveOld && oldDataEnumerator.MoveNext();
|
||||||
|
bool moveNewResult = moveNew && newDataEnumerator.MoveNext();
|
||||||
|
|
||||||
|
if (!(moveOldResult || moveNewResult))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EntityAchievement? oldEntity = oldDataEnumerator.Current;
|
||||||
|
EntityAchievement? newEntity = newDataEnumerator.Current;
|
||||||
|
|
||||||
|
if (oldEntity == null && newEntity != null)
|
||||||
|
{
|
||||||
|
AddEntity(newEntity);
|
||||||
|
add++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (oldEntity != null && newEntity == null)
|
||||||
|
{
|
||||||
|
RemoveEntity(oldEntity);
|
||||||
|
remove++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldEntity!.Id < newEntity!.Id)
|
||||||
|
{
|
||||||
|
moveOld = true;
|
||||||
|
moveNew = false;
|
||||||
|
RemoveEntity(oldEntity);
|
||||||
|
remove++;
|
||||||
|
}
|
||||||
|
else if (oldEntity.Id == newEntity.Id)
|
||||||
|
{
|
||||||
|
moveOld = true;
|
||||||
|
moveNew = true;
|
||||||
|
|
||||||
|
if (oldEntity.Equals(newEntity))
|
||||||
|
{
|
||||||
|
// skip same entry.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemoveEntity(oldEntity);
|
||||||
|
AddEntity(newEntity);
|
||||||
|
update++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// entity.Id > uiaf.Id
|
||||||
|
moveOld = false;
|
||||||
|
moveNew = true;
|
||||||
|
AddEntity(newEntity);
|
||||||
|
add++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(add, update, remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddEntity(EntityAchievement entity)
|
||||||
|
{
|
||||||
|
appDbContext.Achievements.Add(entity);
|
||||||
|
appDbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveEntity(EntityAchievement entity)
|
||||||
|
{
|
||||||
|
appDbContext.Achievements.Remove(entity);
|
||||||
|
appDbContext.SaveChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Achievement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 存档添加结果
|
||||||
|
/// </summary>
|
||||||
|
public enum ArchiveAddResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 添加成功
|
||||||
|
/// </summary>
|
||||||
|
Added,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 名称无效
|
||||||
|
/// </summary>
|
||||||
|
InvalidName,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已经存在该用户
|
||||||
|
/// </summary>
|
||||||
|
AlreadyExists,
|
||||||
|
}
|
||||||
@@ -1,9 +1,66 @@
|
|||||||
namespace Snap.Hutao.Service.Achievement;
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Model.InterChange.Achievement;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement;
|
||||||
|
using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
|
||||||
|
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Achievement;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 成就服务抽象
|
/// 成就服务抽象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IAchievementService
|
internal interface IAchievementService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 当前存档
|
||||||
|
/// </summary>
|
||||||
|
EntityArchive? CurrentArchive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取整合的成就
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="archive">用户</param>
|
||||||
|
/// <param name="metadata">元数据</param>
|
||||||
|
/// <returns>整合的成就</returns>
|
||||||
|
List<BindingAchievement> GetAchievements(EntityArchive archive, IList<MetadataAchievement> metadata);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用于绑定的成就存档集合
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>成就存档集合</returns>
|
||||||
|
ObservableCollection<EntityArchive> GetArchiveCollection();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导入UIAF数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="archive">用户</param>
|
||||||
|
/// <param name="list">UIAF数据</param>
|
||||||
|
/// <param name="option">选项</param>
|
||||||
|
/// <returns>导入</returns>
|
||||||
|
ImportResult ImportFromUIAF(EntityArchive archive, List<UIAFItem> list, ImportOption option);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步移除存档
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="archive">待移除的存档</param>
|
||||||
|
/// <returns>任务</returns>
|
||||||
|
Task RemoveArchiveAsync(EntityArchive archive);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保存成就
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="archive">用户</param>
|
||||||
|
/// <param name="achievements">成就</param>
|
||||||
|
void SaveAchievements(EntityArchive archive, IList<BindingAchievement> achievements);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试添加存档
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newArchive">新存档</param>
|
||||||
|
/// <returns>存档添加结果</returns>
|
||||||
|
Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Achievement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导入选项
|
||||||
|
/// </summary>
|
||||||
|
public enum ImportOption
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 贪婪合并
|
||||||
|
/// </summary>
|
||||||
|
AggressiveMerge = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 懒惰合并
|
||||||
|
/// </summary>
|
||||||
|
LazyMerge = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 完全覆盖
|
||||||
|
/// </summary>
|
||||||
|
Overwrite = 2,
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Achievement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导入结果
|
||||||
|
/// </summary>
|
||||||
|
public struct ImportResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 新增数
|
||||||
|
/// </summary>
|
||||||
|
public readonly int Add;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新数
|
||||||
|
/// </summary>
|
||||||
|
public readonly int Update;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移除数
|
||||||
|
/// </summary>
|
||||||
|
public readonly int Remove;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的导入结果
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="add">添加数</param>
|
||||||
|
/// <param name="update">更新数</param>
|
||||||
|
/// <param name="remove">移除数</param>
|
||||||
|
public ImportResult(int add, int update, int remove)
|
||||||
|
{
|
||||||
|
Add = add;
|
||||||
|
Update = update;
|
||||||
|
Remove = remove;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ internal class InfoBarService : IInfoBarService
|
|||||||
private readonly TaskCompletionSource initializaionCompletionSource = new();
|
private readonly TaskCompletionSource initializaionCompletionSource = new();
|
||||||
private StackPanel? infoBarStack;
|
private StackPanel? infoBarStack;
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Initialize(StackPanel container)
|
public void Initialize(StackPanel container)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Snap.Hutao.Context.Database;
|
using Snap.Hutao.Context.Database;
|
||||||
using Snap.Hutao.Model.Binding;
|
using Snap.Hutao.Model.Binding;
|
||||||
using Snap.Hutao.Service.Abstraction;
|
using Snap.Hutao.Service.Abstraction;
|
||||||
@@ -22,6 +23,7 @@ internal class UserService : IUserService
|
|||||||
private readonly AppDbContext appDbContext;
|
private readonly AppDbContext appDbContext;
|
||||||
private readonly UserClient userClient;
|
private readonly UserClient userClient;
|
||||||
private readonly UserGameRoleClient userGameRoleClient;
|
private readonly UserGameRoleClient userGameRoleClient;
|
||||||
|
private readonly IMessenger messenger;
|
||||||
|
|
||||||
private User? currentUser;
|
private User? currentUser;
|
||||||
private ObservableCollection<User>? userCollection = null;
|
private ObservableCollection<User>? userCollection = null;
|
||||||
@@ -32,11 +34,13 @@ internal class UserService : IUserService
|
|||||||
/// <param name="appDbContext">应用程序数据库上下文</param>
|
/// <param name="appDbContext">应用程序数据库上下文</param>
|
||||||
/// <param name="userClient">用户客户端</param>
|
/// <param name="userClient">用户客户端</param>
|
||||||
/// <param name="userGameRoleClient">角色客户端</param>
|
/// <param name="userGameRoleClient">角色客户端</param>
|
||||||
public UserService(AppDbContext appDbContext, UserClient userClient, UserGameRoleClient userGameRoleClient)
|
/// <param name="messenger">消息器</param>
|
||||||
|
public UserService(AppDbContext appDbContext, UserClient userClient, UserGameRoleClient userGameRoleClient, IMessenger messenger)
|
||||||
{
|
{
|
||||||
this.appDbContext = appDbContext;
|
this.appDbContext = appDbContext;
|
||||||
this.userClient = userClient;
|
this.userClient = userClient;
|
||||||
this.userGameRoleClient = userGameRoleClient;
|
this.userGameRoleClient = userGameRoleClient;
|
||||||
|
this.messenger = messenger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -45,6 +49,11 @@ internal class UserService : IUserService
|
|||||||
get => currentUser;
|
get => currentUser;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
if (currentUser == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// only update when not processing a deletion
|
// only update when not processing a deletion
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
@@ -56,6 +65,8 @@ internal class UserService : IUserService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message.UserChangedMessage message = new(currentUser, value);
|
||||||
|
|
||||||
// 当删除到无用户时也能正常反应状态
|
// 当删除到无用户时也能正常反应状态
|
||||||
currentUser = value;
|
currentUser = value;
|
||||||
|
|
||||||
@@ -65,6 +76,8 @@ internal class UserService : IUserService
|
|||||||
appDbContext.Users.Update(currentUser.Entity);
|
appDbContext.Users.Update(currentUser.Entity);
|
||||||
appDbContext.SaveChanges();
|
appDbContext.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
messenger.Send(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +122,10 @@ internal class UserService : IUserService
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Task RemoveUserAsync(User user)
|
public Task RemoveUserAsync(User user)
|
||||||
{
|
{
|
||||||
|
Must.NotNull(userCollection!);
|
||||||
|
|
||||||
// Sync cache
|
// Sync cache
|
||||||
userCollection!.Remove(user);
|
userCollection.Remove(user);
|
||||||
|
|
||||||
// Sync database
|
// Sync database
|
||||||
appDbContext.Users.Remove(user.Entity);
|
appDbContext.Users.Remove(user.Entity);
|
||||||
@@ -152,7 +167,7 @@ internal class UserService : IUserService
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Task<User?> CreateUserAsync(string cookie)
|
public Task<User?> CreateUserAsync(string cookie)
|
||||||
{
|
{
|
||||||
return User.CreateAsync(new() { Cookie = cookie }, userClient, userGameRoleClient);
|
return User.CreateAsync(Model.Entity.User.Create(cookie), userClient, userGameRoleClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
|
||||||
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
|
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
|
||||||
<UseWinUI>true</UseWinUI>
|
<UseWinUI>true</UseWinUI>
|
||||||
<ImplicitUsings>false</ImplicitUsings>
|
<UseWPF>False</UseWPF>
|
||||||
|
<UseWindowsForms>False</UseWindowsForms>
|
||||||
|
<ImplicitUsings>False</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||||
<NeutralLanguage>zh-CN</NeutralLanguage>
|
<NeutralLanguage>zh-CN</NeutralLanguage>
|
||||||
@@ -26,23 +28,20 @@
|
|||||||
<StartupObject>Snap.Hutao.Program</StartupObject>
|
<StartupObject>Snap.Hutao.Program</StartupObject>
|
||||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT</DefineConstants>
|
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Resource\Icon\UI_BagTabIcon_Avatar.png" />
|
<None Remove="Resource\Icon\UI_BagTabIcon_Avatar.png" />
|
||||||
<None Remove="Resource\Icon\UI_BagTabIcon_Weapon.png" />
|
<None Remove="Resource\Icon\UI_BagTabIcon_Weapon.png" />
|
||||||
<None Remove="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
<None Remove="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||||
<None Remove="Resource\Icon\UI_Icon_Achievement.png" />
|
<None Remove="Resource\Icon\UI_Icon_Achievement.png" />
|
||||||
|
<None Remove="Resource\Icon\UI_ItemIcon_201.png" />
|
||||||
<None Remove="Resource\Segoe Fluent Icons.ttf" />
|
<None Remove="Resource\Segoe Fluent Icons.ttf" />
|
||||||
<None Remove="stylecop.json" />
|
<None Remove="stylecop.json" />
|
||||||
<None Remove="View\Control\DescParamComboBox.xaml" />
|
<None Remove="View\Control\DescParamComboBox.xaml" />
|
||||||
<None Remove="View\Control\ItemIcon.xaml" />
|
<None Remove="View\Control\ItemIcon.xaml" />
|
||||||
<None Remove="View\Control\SkillPivot.xaml" />
|
<None Remove="View\Control\SkillPivot.xaml" />
|
||||||
|
<None Remove="View\Dialog\AchievementArchiveCreateDialog.xaml" />
|
||||||
|
<None Remove="View\Dialog\AchievementImportDialog.xaml" />
|
||||||
<None Remove="View\Dialog\UserDialog.xaml" />
|
<None Remove="View\Dialog\UserDialog.xaml" />
|
||||||
<None Remove="View\MainView.xaml" />
|
<None Remove="View\MainView.xaml" />
|
||||||
<None Remove="View\Page\AchievementPage.xaml" />
|
<None Remove="View\Page\AchievementPage.xaml" />
|
||||||
@@ -71,6 +70,7 @@
|
|||||||
<Content Include="Resource\Icon\UI_BagTabIcon_Weapon.png" />
|
<Content Include="Resource\Icon\UI_BagTabIcon_Weapon.png" />
|
||||||
<Content Include="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
<Content Include="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||||
<Content Include="Resource\Icon\UI_Icon_Achievement.png" />
|
<Content Include="Resource\Icon\UI_Icon_Achievement.png" />
|
||||||
|
<Content Include="Resource\Icon\UI_ItemIcon_201.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -78,20 +78,19 @@
|
|||||||
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
|
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Media" Version="7.1.2" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.8" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.7" />
|
<!-- The PrivateAssets & IncludeAssets of Microsoft.EntityFrameworkCore.Tools should be remove to prevent multiple deps files-->
|
||||||
<!-- The PrivateAssets & IncludeAssets of Microsoft.EntityFrameworkCore.Tools should be remove to prevent multiple deps file-->
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.8" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.7" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.2.32" />
|
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.3.44">
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.2.32">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.0.64" />
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.3" />
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.4" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
@@ -114,6 +113,16 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Dialog\AchievementArchiveCreateDialog.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Dialog\AchievementImportDialog.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Update="View\Control\DescParamComboBox.xaml">
|
<Page Update="View\Control\DescParamComboBox.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<ContentDialog
|
||||||
|
x:Class="Snap.Hutao.View.Dialog.AchievementArchiveCreateDialog"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Snap.Hutao.View.Dialog"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="设置成就存档的名称"
|
||||||
|
DefaultButton="Primary"
|
||||||
|
PrimaryButtonText="确认"
|
||||||
|
CloseButtonText="取消"
|
||||||
|
Style="{StaticResource DefaultContentDialogStyle}">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<TextBox
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
x:Name="InputText"
|
||||||
|
PlaceholderText="在此处输入"
|
||||||
|
VerticalAlignment="Top"/>
|
||||||
|
</Grid>
|
||||||
|
</ContentDialog>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.View.Dialog;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成就存档创建对话框
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AchievementArchiveCreateDialog : ContentDialog
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的成就存档创建对话框
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">窗体</param>
|
||||||
|
public AchievementArchiveCreateDialog(Window window)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
XamlRoot = window.Content.XamlRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取输入的字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>输入的结果</returns>
|
||||||
|
public async Task<Result<bool, string>> GetInputAsync()
|
||||||
|
{
|
||||||
|
ContentDialogResult result = await ShowAsync();
|
||||||
|
string text = InputText.Text ?? string.Empty;
|
||||||
|
|
||||||
|
return new(result == ContentDialogResult.Primary, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<ContentDialog
|
||||||
|
x:Class="Snap.Hutao.View.Dialog.AchievementImportDialog"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
|
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="为当前存档导入成就"
|
||||||
|
DefaultButton="Primary"
|
||||||
|
PrimaryButtonText="确认"
|
||||||
|
CloseButtonText="取消"
|
||||||
|
Style="{StaticResource DefaultContentDialogStyle}">
|
||||||
|
|
||||||
|
<mxi:Interaction.Behaviors>
|
||||||
|
<shcb:ContentDialogBehavior/>
|
||||||
|
</mxi:Interaction.Behaviors>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<cwuc:UniformGrid
|
||||||
|
Grid.Row="0"
|
||||||
|
Columns="3"
|
||||||
|
ColumnSpacing="16"
|
||||||
|
RowSpacing="16">
|
||||||
|
<cwuc:HeaderedContentControl Header="导出App">
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Opacity="0.6"
|
||||||
|
Margin="0,4,0,0"
|
||||||
|
Text="{x:Bind UIAF.Info.ExportApp,Mode=OneWay,TargetNullValue=未知}"/>
|
||||||
|
</cwuc:HeaderedContentControl>
|
||||||
|
<cwuc:HeaderedContentControl Header="导出时间">
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Opacity="0.6"
|
||||||
|
Margin="0,4,0,0"
|
||||||
|
Text="{x:Bind UIAF.Info.ExportDateTime.LocalDateTime,Mode=OneWay,TargetNullValue=未知}"/>
|
||||||
|
</cwuc:HeaderedContentControl>
|
||||||
|
<cwuc:HeaderedContentControl Header="导出App版本">
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Opacity="0.6"
|
||||||
|
Margin="0,4,0,0"
|
||||||
|
Text="{x:Bind UIAF.Info.ExportAppVersion,Mode=OneWay,TargetNullValue=未知}"/>
|
||||||
|
</cwuc:HeaderedContentControl>
|
||||||
|
<cwuc:HeaderedContentControl Header="UIAF 版本">
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Opacity="0.6"
|
||||||
|
Margin="0,4,0,0"
|
||||||
|
Text="{x:Bind UIAF.Info.UIAFVersion,Mode=OneWay,TargetNullValue=未知}"/>
|
||||||
|
</cwuc:HeaderedContentControl>
|
||||||
|
<cwuc:HeaderedContentControl Header="成就个数">
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Opacity="0.6"
|
||||||
|
Margin="0,4,0,0"
|
||||||
|
Text="{x:Bind UIAF.List.Count,Mode=OneWay,TargetNullValue=未知}"/>
|
||||||
|
</cwuc:HeaderedContentControl>
|
||||||
|
</cwuc:UniformGrid>
|
||||||
|
<RadioButtons
|
||||||
|
Name="ImportModeSelector"
|
||||||
|
Header="导入模式"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0,16,0,0"
|
||||||
|
SelectedIndex="0">
|
||||||
|
<RadioButton Content="贪婪(添加新数据,更新已完成项)"/>
|
||||||
|
<RadioButton Content="懒惰(添加新数据,跳过已完成项)"/>
|
||||||
|
<RadioButton Content="覆盖(删除老数据,添加新的数据)"/>
|
||||||
|
</RadioButtons>
|
||||||
|
</Grid>
|
||||||
|
</ContentDialog>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Snap.Hutao.Core;
|
||||||
|
using Snap.Hutao.Core.Threading;
|
||||||
|
using Snap.Hutao.Model.InterChange.Achievement;
|
||||||
|
using Snap.Hutao.Service.Achievement;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.View.Dialog;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成就对话框
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AchievementImportDialog : ContentDialog
|
||||||
|
{
|
||||||
|
private static readonly DependencyProperty UIAFProperty = Property<AchievementImportDialog>.Depend(nameof(UIAF), default(UIAF));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的成就对话框
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">呈现的父窗口</param>
|
||||||
|
/// <param name="uiaf">uiaf数据</param>
|
||||||
|
public AchievementImportDialog(Window window, UIAF uiaf)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
XamlRoot = window.Content.XamlRoot;
|
||||||
|
UIAF = uiaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UIAF数据
|
||||||
|
/// </summary>
|
||||||
|
public UIAF UIAF
|
||||||
|
{
|
||||||
|
get { return (UIAF)GetValue(UIAFProperty); }
|
||||||
|
set { SetValue(UIAFProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步获取导入选项
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>导入选项</returns>
|
||||||
|
public async Task<Result<bool, ImportOption>> GetImportOptionAsync()
|
||||||
|
{
|
||||||
|
ContentDialogResult result = await ShowAsync();
|
||||||
|
ImportOption option = (ImportOption)ImportModeSelector.SelectedIndex;
|
||||||
|
|
||||||
|
return new Result<bool, ImportOption>(result == ContentDialogResult.Primary, option);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,13 +22,53 @@
|
|||||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||||
</mxi:Interaction.Behaviors>
|
</mxi:Interaction.Behaviors>
|
||||||
<Grid>
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<CommandBar
|
||||||
|
Grid.Row="0"
|
||||||
|
DefaultLabelPosition="Right">
|
||||||
|
<CommandBar.Background>
|
||||||
|
<SolidColorBrush Color="{ThemeResource CardBackgroundFillColorSecondary}"/>
|
||||||
|
</CommandBar.Background>
|
||||||
|
|
||||||
|
<AppBarElementContainer>
|
||||||
|
<ComboBox
|
||||||
|
MinWidth="160"
|
||||||
|
Height="36"
|
||||||
|
Margin="2,6,3,6"
|
||||||
|
DisplayMemberPath="Name"
|
||||||
|
ItemsSource="{Binding Archives,Mode=OneWay}"
|
||||||
|
SelectedItem="{Binding SelectedArchive,Mode=TwoWay}"/>
|
||||||
|
</AppBarElementContainer>
|
||||||
|
<CommandBar.SecondaryCommands>
|
||||||
|
<AppBarButton
|
||||||
|
Icon="Add"
|
||||||
|
Label="创建新存档"
|
||||||
|
Command="{Binding AddArchiveCommand}"/>
|
||||||
|
<AppBarButton
|
||||||
|
Icon="Delete"
|
||||||
|
Label="删除当前存档"
|
||||||
|
Command="{Binding RemoveArchiveCommand}"/>
|
||||||
|
<AppBarSeparator/>
|
||||||
|
<AppBarButton
|
||||||
|
Icon="Paste"
|
||||||
|
Label="从剪贴板导入"
|
||||||
|
Command="{Binding ImportUIAFFromClipboardCommand}"/>
|
||||||
|
<AppBarButton
|
||||||
|
Icon="OpenFile"
|
||||||
|
Label="从UIAF文件导入"
|
||||||
|
Command="{Binding ImportUIAFFromFileCommand}"/>
|
||||||
|
</CommandBar.SecondaryCommands>
|
||||||
|
|
||||||
|
</CommandBar>
|
||||||
<SplitView
|
<SplitView
|
||||||
|
Grid.Row="1"
|
||||||
IsPaneOpen="True"
|
IsPaneOpen="True"
|
||||||
DisplayMode="Inline"
|
DisplayMode="Inline"
|
||||||
OpenPaneLength="252">
|
OpenPaneLength="252"
|
||||||
<SplitView.PaneBackground>
|
PaneBackground="Transparent">
|
||||||
<SolidColorBrush Color="{ThemeResource CardBackgroundFillColorSecondary}"/>
|
|
||||||
</SplitView.PaneBackground>
|
|
||||||
<SplitView.Pane>
|
<SplitView.Pane>
|
||||||
<ListView
|
<ListView
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
@@ -62,8 +102,7 @@
|
|||||||
<ItemsControl
|
<ItemsControl
|
||||||
Margin="16,0,0,16"
|
Margin="16,0,0,16"
|
||||||
ItemsSource="{Binding Achievements}">
|
ItemsSource="{Binding Achievements}">
|
||||||
<!--ContentThemeTransition here can make items blinking,
|
<!--ContentThemeTransition here can make items blinking, cause we are using ItemsStackPanel-->
|
||||||
cause we are using ItemsStackPanel-->
|
|
||||||
<!--<ItemsControl.Transitions>
|
<!--<ItemsControl.Transitions>
|
||||||
<ContentThemeTransition/>
|
<ContentThemeTransition/>
|
||||||
</ItemsControl.Transitions>-->
|
</ItemsControl.Transitions>-->
|
||||||
@@ -86,28 +125,40 @@
|
|||||||
MinHeight="48">
|
MinHeight="48">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<!-- Icon -->
|
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<!-- Header and subtitle -->
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<!-- Action control -->
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
|
IsChecked="{Binding IsChecked,Mode=TwoWay}"
|
||||||
Margin="6,0,0,0"
|
Margin="6,0,0,0"
|
||||||
Style="{StaticResource DefaultCheckBoxStyle}"
|
Style="{StaticResource DefaultCheckBoxStyle}"
|
||||||
Padding="16,0,0,0"
|
Padding="16,0,0,0"
|
||||||
Grid.Column="1">
|
Grid.Column="1">
|
||||||
<CheckBox.Content>
|
<CheckBox.Content>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="{Binding Title}"/>
|
<TextBlock Text="{Binding Inner.Title}"/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="0,2,0,0"
|
Margin="0,2,0,0"
|
||||||
Style="{StaticResource SecondaryTextStyle}"
|
Style="{StaticResource SecondaryTextStyle}"
|
||||||
Text="{Binding Description}"/>
|
Text="{Binding Inner.Description}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</CheckBox.Content>
|
</CheckBox.Content>
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
<Grid Grid.Column="3">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="32"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Image
|
||||||
|
Height="32"
|
||||||
|
Source="ms-appx:///Resource/Icon/UI_ItemIcon_201.png"/>
|
||||||
|
<TextBlock
|
||||||
|
Margin="12,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Grid.Column="1"
|
||||||
|
Text="{Binding Inner.FinishReward.Count}"/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
@@ -115,6 +166,5 @@
|
|||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</SplitView.Content>
|
</SplitView.Content>
|
||||||
</SplitView>
|
</SplitView>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</shcc:CancellablePage>
|
</shcc:CancellablePage>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
using Snap.Hutao.Control.Cancellable;
|
using Snap.Hutao.Control.Cancellable;
|
||||||
using Snap.Hutao.Service.Navigation;
|
using Snap.Hutao.Service.Navigation;
|
||||||
@@ -32,4 +33,12 @@ public sealed partial class AchievementPage : CancellablePage
|
|||||||
extra.NotifyNavigationCompleted();
|
extra.NotifyNavigationCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
||||||
|
{
|
||||||
|
((AchievementViewModel)DataContext).SaveAchievements();
|
||||||
|
|
||||||
|
base.OnNavigatingFrom(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
using Microsoft.VisualStudio.Threading;
|
|
||||||
using Microsoft.Web.WebView2.Core;
|
using Microsoft.Web.WebView2.Core;
|
||||||
using Snap.Hutao.Extension;
|
using Snap.Hutao.Extension;
|
||||||
using Snap.Hutao.Service.Navigation;
|
using Snap.Hutao.Service.Navigation;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Snap.Hutao.View.TitleView"
|
x:Class="Snap.Hutao.View.TitleView"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="using:Snap.Hutao.View"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
|
|||||||
@@ -2,12 +2,28 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using CommunityToolkit.WinUI.UI;
|
using CommunityToolkit.WinUI.UI;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Snap.Hutao.Control;
|
||||||
using Snap.Hutao.Control.Cancellable;
|
using Snap.Hutao.Control.Cancellable;
|
||||||
|
using Snap.Hutao.Extension;
|
||||||
using Snap.Hutao.Factory.Abstraction;
|
using Snap.Hutao.Factory.Abstraction;
|
||||||
|
using Snap.Hutao.Message;
|
||||||
|
using Snap.Hutao.Model.InterChange.Achievement;
|
||||||
using Snap.Hutao.Model.Metadata.Achievement;
|
using Snap.Hutao.Model.Metadata.Achievement;
|
||||||
|
using Snap.Hutao.Service.Abstraction;
|
||||||
|
using Snap.Hutao.Service.Achievement;
|
||||||
using Snap.Hutao.Service.Metadata;
|
using Snap.Hutao.Service.Metadata;
|
||||||
|
using Snap.Hutao.View.Dialog;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
|
using Windows.Storage;
|
||||||
|
using Windows.Storage.Pickers;
|
||||||
|
using Windows.Storage.Streams;
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel;
|
namespace Snap.Hutao.ViewModel;
|
||||||
|
|
||||||
@@ -15,27 +31,86 @@ namespace Snap.Hutao.ViewModel;
|
|||||||
/// 成就视图模型
|
/// 成就视图模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injection(InjectAs.Transient)]
|
[Injection(InjectAs.Transient)]
|
||||||
internal class AchievementViewModel : ObservableObject, ISupportCancellation
|
internal class AchievementViewModel
|
||||||
|
: ObservableObject,
|
||||||
|
ISupportCancellation,
|
||||||
|
IRecipient<AchievementArchiveChangedMessage>,
|
||||||
|
IRecipient<MainWindowClosedMessage>
|
||||||
{
|
{
|
||||||
private readonly IMetadataService metadataService;
|
private readonly IMetadataService metadataService;
|
||||||
|
private readonly IAchievementService achievementService;
|
||||||
|
private readonly IInfoBarService infoBarService;
|
||||||
|
private readonly JsonSerializerOptions options;
|
||||||
|
private readonly IPickerFactory pickerFactory;
|
||||||
|
|
||||||
private AdvancedCollectionView? achievements;
|
private AdvancedCollectionView? achievements;
|
||||||
private IList<AchievementGoal>? achievementGoals;
|
private IList<AchievementGoal>? achievementGoals;
|
||||||
private AchievementGoal? selectedAchievementGoal;
|
private AchievementGoal? selectedAchievementGoal;
|
||||||
|
private ObservableCollection<Model.Entity.AchievementArchive>? archives;
|
||||||
|
private Model.Entity.AchievementArchive? selectedArchive;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的成就视图模型
|
/// 构造一个新的成就视图模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="metadataService">元数据服务</param>
|
/// <param name="metadataService">元数据服务</param>
|
||||||
|
/// <param name="achievementService">成就服务</param>
|
||||||
|
/// <param name="infoBarService">信息条服务</param>
|
||||||
|
/// <param name="options">Json序列化选项</param>
|
||||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||||
public AchievementViewModel(IMetadataService metadataService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
|
/// <param name="pickerFactory">文件选择器工厂</param>
|
||||||
|
/// <param name="messenger">消息器</param>
|
||||||
|
public AchievementViewModel(
|
||||||
|
IMetadataService metadataService,
|
||||||
|
IAchievementService achievementService,
|
||||||
|
IInfoBarService infoBarService,
|
||||||
|
JsonSerializerOptions options,
|
||||||
|
IAsyncRelayCommandFactory asyncRelayCommandFactory,
|
||||||
|
IPickerFactory pickerFactory,
|
||||||
|
IMessenger messenger)
|
||||||
{
|
{
|
||||||
this.metadataService = metadataService;
|
this.metadataService = metadataService;
|
||||||
|
this.achievementService = achievementService;
|
||||||
|
this.infoBarService = infoBarService;
|
||||||
|
this.options = options;
|
||||||
|
this.pickerFactory = pickerFactory;
|
||||||
|
|
||||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||||
|
ImportUIAFFromClipboardCommand = asyncRelayCommandFactory.Create(ImportUIAFFromClipboardAsync);
|
||||||
|
ImportUIAFFromFileCommand = asyncRelayCommandFactory.Create(ImportUIAFFromFileAsync);
|
||||||
|
AddArchiveCommand = asyncRelayCommandFactory.Create(AddArchiveAsync);
|
||||||
|
RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync);
|
||||||
|
|
||||||
|
messenger.Register<AchievementArchiveChangedMessage>(this);
|
||||||
|
messenger.Register<MainWindowClosedMessage>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public CancellationToken CancellationToken { get; set; }
|
public CancellationToken CancellationToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成就存档集合
|
||||||
|
/// </summary>
|
||||||
|
public ObservableCollection<Model.Entity.AchievementArchive>? Archives
|
||||||
|
{
|
||||||
|
get => archives;
|
||||||
|
set => SetProperty(ref archives, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选中的成就存档
|
||||||
|
/// </summary>
|
||||||
|
public Model.Entity.AchievementArchive? SelectedArchive
|
||||||
|
{
|
||||||
|
get => selectedArchive;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref selectedArchive, value))
|
||||||
|
{
|
||||||
|
achievementService.CurrentArchive = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 成就视图
|
/// 成就视图
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -63,7 +138,7 @@ internal class AchievementViewModel : ObservableObject, ISupportCancellation
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
SetProperty(ref selectedAchievementGoal, value);
|
SetProperty(ref selectedAchievementGoal, value);
|
||||||
OnGoalChanged(value);
|
UpdateAchievementFilter(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,21 +147,249 @@ internal class AchievementViewModel : ObservableObject, ISupportCancellation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ICommand OpenUICommand { get; }
|
public ICommand OpenUICommand { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加存档命令
|
||||||
|
/// </summary>
|
||||||
|
public ICommand AddArchiveCommand { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除存档命令
|
||||||
|
/// </summary>
|
||||||
|
public ICommand RemoveArchiveCommand { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从剪贴板导入UIAF命令
|
||||||
|
/// </summary>
|
||||||
|
public ICommand ImportUIAFFromClipboardCommand { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从文件导入UIAF命令
|
||||||
|
/// </summary>
|
||||||
|
public ICommand ImportUIAFFromFileCommand { 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()
|
||||||
|
{
|
||||||
|
if (Achievements != null && SelectedArchive != null)
|
||||||
|
{
|
||||||
|
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement>)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleArchiveChangeAsync(Model.Entity.AchievementArchive? oldArchieve, Model.Entity.AchievementArchive? newArchieve)
|
||||||
|
{
|
||||||
|
if (oldArchieve != null && Achievements != null)
|
||||||
|
{
|
||||||
|
achievementService.SaveAchievements(oldArchieve, (Achievements.Source as IList<Model.Binding.Achievement>)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newArchieve != null)
|
||||||
|
{
|
||||||
|
await UpdateAchievementsAsync(newArchieve);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
infoBarService.Warning("请创建或选择一个成就存档");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OpenUIAsync()
|
private async Task OpenUIAsync()
|
||||||
{
|
{
|
||||||
if (await metadataService.InitializeAsync(CancellationToken))
|
if (await metadataService.InitializeAsync(CancellationToken))
|
||||||
{
|
{
|
||||||
Achievements = new(await metadataService.GetAchievementsAsync(CancellationToken), true);
|
|
||||||
AchievementGoals = await metadataService.GetAchievementGoalsAsync(CancellationToken);
|
AchievementGoals = await metadataService.GetAchievementGoalsAsync(CancellationToken);
|
||||||
|
|
||||||
|
Archives = achievementService.GetArchiveCollection();
|
||||||
|
|
||||||
|
SelectedArchive = Archives.SingleOrDefault(a => a.IsSelected == true);
|
||||||
|
|
||||||
|
if (SelectedArchive == null)
|
||||||
|
{
|
||||||
|
infoBarService.Warning("请创建或选择一个成就存档");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGoalChanged(AchievementGoal? goal)
|
private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive)
|
||||||
|
{
|
||||||
|
List<Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken);
|
||||||
|
List<Model.Binding.Achievement> combined = achievementService.GetAchievements(archive, rawAchievements);
|
||||||
|
Achievements = new(combined, true);
|
||||||
|
|
||||||
|
UpdateAchievementFilter(SelectedAchievementGoal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddArchiveAsync()
|
||||||
|
{
|
||||||
|
(bool isOk, string name) = await new AchievementArchiveCreateDialog(App.Window!).GetInputAsync();
|
||||||
|
|
||||||
|
if (isOk)
|
||||||
|
{
|
||||||
|
ArchiveAddResult result = await achievementService.TryAddArchiveAsync(Model.Entity.AchievementArchive.Create(name));
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case ArchiveAddResult.Added:
|
||||||
|
infoBarService.Success($"存档 [{name}] 添加成功");
|
||||||
|
break;
|
||||||
|
case ArchiveAddResult.InvalidName:
|
||||||
|
infoBarService.Information($"不能添加名称无效的存档");
|
||||||
|
break;
|
||||||
|
case ArchiveAddResult.AlreadyExists:
|
||||||
|
infoBarService.Information($"不能添加名称重复的存档 [{name}]");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Must.NeverHappen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveArchiveAsync()
|
||||||
|
{
|
||||||
|
if (Archives != null && SelectedArchive != null)
|
||||||
|
{
|
||||||
|
ContentDialogResult result = await new ContentDialog2(App.Window!)
|
||||||
|
{
|
||||||
|
Title = $"确定要删除存档 {SelectedArchive.Name} 吗?",
|
||||||
|
Content = "该操作是不可逆的,该存档和其内的所有成就状态会丢失。",
|
||||||
|
PrimaryButtonText = "确认",
|
||||||
|
CloseButtonText = "取消",
|
||||||
|
DefaultButton = ContentDialogButton.Close,
|
||||||
|
}
|
||||||
|
.ShowAsync();
|
||||||
|
|
||||||
|
if (result == ContentDialogResult.Primary)
|
||||||
|
{
|
||||||
|
await achievementService.RemoveArchiveAsync(SelectedArchive);
|
||||||
|
|
||||||
|
// reselect first archive
|
||||||
|
SelectedArchive = Archives.FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ImportUIAFFromClipboardAsync()
|
||||||
|
{
|
||||||
|
if (achievementService.CurrentArchive == null)
|
||||||
|
{
|
||||||
|
// TODO: automatically create a archive.
|
||||||
|
infoBarService.Information("必须选择一个用户才能导入成就");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataPackageView view = Clipboard.GetContent();
|
||||||
|
string json = await view.GetTextAsync();
|
||||||
|
|
||||||
|
UIAF? uiaf = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uiaf = JsonSerializer.Deserialize<UIAF>(json, options);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
infoBarService?.Error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uiaf != null)
|
||||||
|
{
|
||||||
|
(bool isOk, ImportOption option) = await new AchievementImportDialog(App.Window!, uiaf).GetImportOptionAsync();
|
||||||
|
|
||||||
|
if (isOk)
|
||||||
|
{
|
||||||
|
ImportResult result = achievementService.ImportFromUIAF(achievementService.CurrentArchive, uiaf.List, option);
|
||||||
|
infoBarService!.Success($"新增:{result.Add} 个成就 | 更新:{result.Update} 个成就 | 删除{result.Remove} 个成就");
|
||||||
|
|
||||||
|
await UpdateAchievementsAsync(achievementService.CurrentArchive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await new ContentDialog2(App.Window!)
|
||||||
|
{
|
||||||
|
Title = "导入失败",
|
||||||
|
Content = "剪贴板中的内容格式不正确",
|
||||||
|
PrimaryButtonText = "确认",
|
||||||
|
DefaultButton = ContentDialogButton.Primary,
|
||||||
|
}
|
||||||
|
.ShowAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ImportUIAFFromFileAsync()
|
||||||
|
{
|
||||||
|
if (achievementService.CurrentArchive == null)
|
||||||
|
{
|
||||||
|
infoBarService.Information("必须选择一个用户才能导入成就");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOpenPicker picker = pickerFactory.GetFileOpenPicker();
|
||||||
|
picker.SuggestedStartLocation = PickerLocationId.Desktop;
|
||||||
|
picker.FileTypeFilter.Add(".json");
|
||||||
|
|
||||||
|
if (await picker.PickSingleFileAsync() is StorageFile file)
|
||||||
|
{
|
||||||
|
UIAF? uiaf = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (IRandomAccessStreamWithContentType fileSream = await file.OpenReadAsync())
|
||||||
|
{
|
||||||
|
using (Stream stream = fileSream.AsStream())
|
||||||
|
{
|
||||||
|
uiaf = await JsonSerializer.DeserializeAsync<UIAF>(stream, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
infoBarService?.Error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uiaf != null)
|
||||||
|
{
|
||||||
|
(bool isOk, ImportOption option) = await new AchievementImportDialog(App.Window!, uiaf).GetImportOptionAsync();
|
||||||
|
|
||||||
|
if (isOk)
|
||||||
|
{
|
||||||
|
ImportResult result = achievementService.ImportFromUIAF(achievementService.CurrentArchive, uiaf.List, option);
|
||||||
|
infoBarService!.Success($"新增:{result.Add} 个成就 | 更新:{result.Update} 个成就 | 删除{result.Remove} 个成就");
|
||||||
|
await UpdateAchievementsAsync(achievementService.CurrentArchive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await new ContentDialog2(App.Window!)
|
||||||
|
{
|
||||||
|
Title = "导入失败",
|
||||||
|
Content = "文件中的内容格式不正确",
|
||||||
|
PrimaryButtonText = "确认",
|
||||||
|
DefaultButton = ContentDialogButton.Primary,
|
||||||
|
}
|
||||||
|
.ShowAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAchievementFilter(AchievementGoal? goal)
|
||||||
{
|
{
|
||||||
if (Achievements != null)
|
if (Achievements != null)
|
||||||
{
|
{
|
||||||
Achievements.Filter = goal != null
|
Achievements.Filter = goal != null
|
||||||
? ((object o) => o is Achievement achi && achi.Goal == goal.Id)
|
? ((object o) => o is Model.Binding.Achievement achi && achi.Inner.Goal == goal.Id)
|
||||||
: ((object o) => true);
|
: ((object o) => true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,13 +123,12 @@ internal class UserViewModel : ObservableObject
|
|||||||
private async Task AddUserAsync()
|
private async Task AddUserAsync()
|
||||||
{
|
{
|
||||||
// Get cookie from user input
|
// Get cookie from user input
|
||||||
Window mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
(bool isOk, string cookie) = await new UserDialog(App.Window!).GetInputCookieAsync();
|
||||||
Result<bool, string> result = await new UserDialog(mainWindow).GetInputCookieAsync();
|
|
||||||
|
|
||||||
// User confirms the input
|
// User confirms the input
|
||||||
if (result.IsOk)
|
if (isOk)
|
||||||
{
|
{
|
||||||
if (TryValidateCookie(userService.ParseCookie(result.Value), out IDictionary<string, string>? filteredCookie))
|
if (TryValidateCookie(userService.ParseCookie(cookie), out IDictionary<string, string>? filteredCookie))
|
||||||
{
|
{
|
||||||
string simplifiedCookie = string.Join(';', filteredCookie.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
string simplifiedCookie = string.Join(';', filteredCookie.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ internal static class HttpClientDynamicSecretExtensions
|
|||||||
/// <param name="httpClient">请求器</param>
|
/// <param name="httpClient">请求器</param>
|
||||||
/// <param name="options">选项</param>
|
/// <param name="options">选项</param>
|
||||||
/// <param name="url">地址</param>
|
/// <param name="url">地址</param>
|
||||||
/// <param name="data">post数据</param>
|
|
||||||
/// <returns>响应</returns>
|
/// <returns>响应</returns>
|
||||||
public static IDynamicSecret2HttpClient UsingDynamicSecret2(this HttpClient httpClient, JsonSerializerOptions options, string url)
|
public static IDynamicSecret2HttpClient UsingDynamicSecret2(this HttpClient httpClient, JsonSerializerOptions options, string url)
|
||||||
{
|
{
|
||||||
@@ -47,7 +46,6 @@ internal static class HttpClientDynamicSecretExtensions
|
|||||||
public static IDynamicSecret2HttpClient<TValue> UsingDynamicSecret2<TValue>(this HttpClient httpClient, JsonSerializerOptions options, string url, TValue data)
|
public static IDynamicSecret2HttpClient<TValue> UsingDynamicSecret2<TValue>(this HttpClient httpClient, JsonSerializerOptions options, string url, TValue data)
|
||||||
where TValue : class
|
where TValue : class
|
||||||
{
|
{
|
||||||
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(options, url, data));
|
|
||||||
return new DynamicSecret2HttpClient<TValue>(httpClient, options, url, data);
|
return new DynamicSecret2HttpClient<TValue>(httpClient, options, url, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,9 @@ using System.Text.Json.Serialization;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
|
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 奖励
|
||||||
|
/// </summary>
|
||||||
public class Reward
|
public class Reward
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -7,35 +7,36 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 签到结果
|
/// 签到结果
|
||||||
|
/// https://docs.geetest.com/sensebot/apirefer/api/server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SignInResult
|
public class SignInResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通常是 ""
|
/// ???
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("code")]
|
[JsonPropertyName("code")]
|
||||||
public string Code { get; set; } = default!;
|
public string Code { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通常是 ""
|
/// 风控码 375
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("risk_code")]
|
[JsonPropertyName("risk_code")]
|
||||||
public int RiskCode { get; set; }
|
public int RiskCode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通常是 ""
|
/// geetest appid
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("gt")]
|
[JsonPropertyName("gt")]
|
||||||
public string Gt { get; set; } = default!;
|
public string Gt { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通常是 ""
|
/// geetest challenge id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("challenge")]
|
[JsonPropertyName("challenge")]
|
||||||
public string Challenge { get; set; } = default!;
|
public string Challenge { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通常是 ""
|
/// geetest 服务状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("success")]
|
[JsonPropertyName("success")]
|
||||||
public int Success { get; set; }
|
public int Success { get; set; }
|
||||||
|
|||||||
Reference in New Issue
Block a user