mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
style
This commit is contained in:
14
README.md
14
README.md
@@ -1,4 +1,16 @@
|
||||
# Snap.Hutao
|
||||
唷,找本堂主有何贵干呀?
|
||||
> 唷,找本堂主有何贵干呀?
|
||||
|
||||

|
||||
|
||||
## 感谢以下项目或人员的帮助
|
||||
|
||||
* [CommunityToolkit/dotnet](https://github.com/CommunityToolkit/dotnet)
|
||||
* [CommunityToolkit/WindowsCommunityToolkit](https://github.com/CommunityToolkit/WindowsCommunityToolkit)
|
||||
* [dotnet/efcore](https://github.com/dotnet/efcore)
|
||||
* [dotnet/runtime](https://github.com/dotnet/runtime)
|
||||
* [DotNetAnalyzers/StyleCopAnalyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers)
|
||||
* [microsoft/vs-threading](https://github.com/microsoft/vs-threading)
|
||||
* [microsoft/vs-validation](https://github.com/microsoft/vs-validation)
|
||||
* [microsoft/WindowsAppSDK](https://github.com/microsoft/WindowsAppSDK)
|
||||
* [microsoft/microsoft-ui-xaml](https://github.com/microsoft/microsoft-ui-xaml)
|
||||
@@ -8,7 +8,6 @@ using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using System.Diagnostics;
|
||||
using Windows.Storage;
|
||||
@@ -23,7 +22,6 @@ public partial class App : Application
|
||||
{
|
||||
private static Window? window;
|
||||
private readonly ILogger<App> logger;
|
||||
private readonly ExceptionRecorder exceptionRecorder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object.
|
||||
@@ -38,7 +36,7 @@ public partial class App : Application
|
||||
// so we can use Ioc here.
|
||||
logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
||||
|
||||
exceptionRecorder = new(this, logger);
|
||||
_ = new ExceptionRecorder(this, logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,8 +71,8 @@ public partial class App : Application
|
||||
|
||||
if (firstInstance.IsCurrent)
|
||||
{
|
||||
OnActivated(firstInstance, activatedEventArgs);
|
||||
firstInstance.Activated += OnActivated;
|
||||
Activation.Activate(firstInstance, activatedEventArgs);
|
||||
firstInstance.Activated += Activation.Activate;
|
||||
|
||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", CacheFolder.Path);
|
||||
|
||||
@@ -116,21 +114,4 @@ public partial class App : Application
|
||||
|
||||
Ioc.Default.ConfigureServices(services);
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD100")]
|
||||
private async void OnActivated(object? sender, AppActivationArguments args)
|
||||
{
|
||||
Window = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
await infoBarService.WaitInitializationAsync();
|
||||
|
||||
if (args.Kind == ExtendedActivationKind.Protocol)
|
||||
{
|
||||
if (args.TryGetProtocolActivatedUri(out Uri? uri))
|
||||
{
|
||||
infoBarService.Information(uri.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Convert;
|
||||
using Snap.Hutao.Extension;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
@@ -39,35 +37,17 @@ internal static class CoreEnvironment
|
||||
/// </summary>
|
||||
public static readonly Version Version;
|
||||
|
||||
/// <summary>
|
||||
/// 设备Id
|
||||
/// </summary>
|
||||
public static readonly string DeviceId;
|
||||
|
||||
/// <summary>
|
||||
/// 米游社设备Id
|
||||
/// </summary>
|
||||
public static readonly string HoyolabDeviceId;
|
||||
|
||||
private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography";
|
||||
private const string MachineGuidValue = "MachineGuid";
|
||||
|
||||
static CoreEnvironment()
|
||||
{
|
||||
Version = Package.Current.Id.Version.ToVersion();
|
||||
CommonUA = $"Snap Hutao/{Version}";
|
||||
DeviceId = GetDeviceId();
|
||||
|
||||
// simply assign a random guid
|
||||
HoyolabDeviceId = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备的UUID
|
||||
/// </summary>
|
||||
/// <returns>设备的UUID</returns>
|
||||
private static string GetDeviceId()
|
||||
{
|
||||
string userName = Environment.UserName;
|
||||
object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName);
|
||||
return Md5Convert.ToHexString($"{userName}{machineGuid}");
|
||||
}
|
||||
}
|
||||
75
src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
Normal file
75
src/Snap.Hutao/Snap.Hutao/Core/Database/DbCurrent.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库当前项
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体的类型</typeparam>
|
||||
/// <typeparam name="TMessage">消息的类型</typeparam>
|
||||
internal class DbCurrent<TEntity, TMessage>
|
||||
where TEntity : class, ISelectable
|
||||
where TMessage : Message.ValueChangedMessage<TEntity>
|
||||
{
|
||||
private readonly DbContext dbContext;
|
||||
private readonly DbSet<TEntity> dbSet;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
private TEntity? current;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的数据库当前项
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文</param>
|
||||
/// <param name="dbSet">数据集</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public DbCurrent(DbContext dbContext, DbSet<TEntity> dbSet, IMessenger messenger)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
this.dbSet = dbSet;
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前项
|
||||
/// </summary>
|
||||
public TEntity? Current
|
||||
{
|
||||
get => current;
|
||||
set
|
||||
{
|
||||
// prevent useless sets
|
||||
if (current == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// only update when not processing a deletion
|
||||
if (value != null)
|
||||
{
|
||||
if (current != null)
|
||||
{
|
||||
current.IsSelected = false;
|
||||
dbSet.Update(current);
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
TMessage message = (TMessage)Activator.CreateInstance(typeof(TMessage), current, value)!;
|
||||
current = value;
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
current.IsSelected = true;
|
||||
dbSet.Update(current);
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
messenger.Send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/Snap.Hutao/Snap.Hutao/Core/Database/ISelectable.cs
Normal file
15
src/Snap.Hutao/Snap.Hutao/Core/Database/ISelectable.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
/// <summary>
|
||||
/// 可选择的项
|
||||
/// </summary>
|
||||
public interface ISelectable
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置当前项的选中状态
|
||||
/// </summary>
|
||||
bool IsSelected { get; set; }
|
||||
}
|
||||
@@ -2,15 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Web.Enka;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
108
src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
Normal file
108
src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
|
||||
namespace Snap.Hutao.Core.LifeCycle;
|
||||
|
||||
/// <summary>
|
||||
/// 激活处理器
|
||||
/// </summary>
|
||||
internal static class Activation
|
||||
{
|
||||
public static volatile bool isActivating = false;
|
||||
public static object activationLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 响应激活事件
|
||||
/// </summary>
|
||||
/// <param name="sender">发送方</param>
|
||||
/// <param name="args">激活参数</param>
|
||||
public static void Activate(object? sender, AppActivationArguments args)
|
||||
{
|
||||
HandleActivationAsync(args).SafeForget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步响应激活事件
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
private static async Task HandleActivationAsync(AppActivationArguments args)
|
||||
{
|
||||
if (isActivating)
|
||||
{
|
||||
lock (activationLock)
|
||||
{
|
||||
if (isActivating)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock (activationLock)
|
||||
{
|
||||
isActivating = true;
|
||||
}
|
||||
|
||||
App.Window = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
await infoBarService.WaitInitializationAsync();
|
||||
|
||||
if (args.Kind == ExtendedActivationKind.Protocol)
|
||||
{
|
||||
if (args.TryGetProtocolActivatedUri(out Uri? uri))
|
||||
{
|
||||
infoBarService.Information(uri.ToString());
|
||||
await HandleUrlActivationAsync(uri);
|
||||
}
|
||||
}
|
||||
|
||||
lock (activationLock)
|
||||
{
|
||||
isActivating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleUrlActivationAsync(Uri uri)
|
||||
{
|
||||
UriBuilder builder = new(uri);
|
||||
Requires.Argument(builder.Scheme == "hutao", nameof(uri), "uri 的协议不正确");
|
||||
|
||||
string category = builder.Host.ToLowerInvariant();
|
||||
string action = builder.Path.ToLowerInvariant();
|
||||
string rawParameter = builder.Query;
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case "achievement":
|
||||
{
|
||||
await HandleAchievementActionAsync(action, rawParameter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleAchievementActionAsync(string action, string parameter)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case "/import":
|
||||
{
|
||||
await Program.UIDispatcherQueue.EnqueueAsync(async () =>
|
||||
{
|
||||
INavigationAwaiter navigationAwaiter = new NavigationExtra("InvokeByUri");
|
||||
await Ioc.Default
|
||||
.GetRequiredService<INavigationService>()
|
||||
.NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ internal static class EventIds
|
||||
/// <summary>
|
||||
/// 异步命令执行异常
|
||||
/// </summary>
|
||||
[Obsolete("异步命令不再通过Factory记录异常")]
|
||||
public static readonly EventId AsyncCommandException = 100002;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -56,5 +56,5 @@ public static class ThemeHelper
|
||||
ElementTheme.Dark => SystemBackdropTheme.Dark,
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Snap.Hutao.Core;
|
||||
/// 检测 WebView2运行时 是否存在
|
||||
/// 不再使用注册表检查方式
|
||||
/// </summary>
|
||||
internal abstract class WebView2Helper
|
||||
internal static class WebView2Helper
|
||||
{
|
||||
private static bool hasEverDetected = false;
|
||||
private static bool isSupported = false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Factory;
|
||||
@@ -10,51 +11,92 @@ namespace Snap.Hutao.Factory;
|
||||
[Injection(InjectAs.Transient, typeof(IAsyncRelayCommandFactory))]
|
||||
internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的异步命令工厂
|
||||
/// </summary>
|
||||
/// <param name="logger">日志器</param>
|
||||
public AsyncRelayCommandFactory(ILogger<AsyncRelayCommandFactory> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AsyncRelayCommand<T> Create<T>(Func<T?, Task> execute)
|
||||
{
|
||||
return new AsyncRelayCommand<T>(execute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
|
||||
return Register(new AsyncRelayCommand<T>(execute));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AsyncRelayCommand<T> Create<T>(Func<T?, CancellationToken, Task> cancelableExecute)
|
||||
{
|
||||
return new AsyncRelayCommand<T>(cancelableExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
|
||||
return Register(new AsyncRelayCommand<T>(cancelableExecute));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AsyncRelayCommand<T> Create<T>(Func<T?, Task> execute, Predicate<T?> canExecute)
|
||||
{
|
||||
return new AsyncRelayCommand<T>(execute, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
|
||||
return Register(new AsyncRelayCommand<T>(execute, canExecute));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AsyncRelayCommand<T> Create<T>(Func<T?, CancellationToken, Task> cancelableExecute, Predicate<T?> canExecute)
|
||||
{
|
||||
return new AsyncRelayCommand<T>(cancelableExecute, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
|
||||
return Register(new AsyncRelayCommand<T>(cancelableExecute, canExecute));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AsyncRelayCommand Create(Func<Task> execute)
|
||||
{
|
||||
return new AsyncRelayCommand(execute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
|
||||
return Register(new AsyncRelayCommand(execute));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AsyncRelayCommand Create(Func<CancellationToken, Task> cancelableExecute)
|
||||
{
|
||||
return new AsyncRelayCommand(cancelableExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
|
||||
return Register(new AsyncRelayCommand(cancelableExecute));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AsyncRelayCommand Create(Func<Task> execute, Func<bool> canExecute)
|
||||
{
|
||||
return new AsyncRelayCommand(execute, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
|
||||
return Register(new AsyncRelayCommand(execute, canExecute));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AsyncRelayCommand Create(Func<CancellationToken, Task> cancelableExecute, Func<bool> canExecute)
|
||||
{
|
||||
return new AsyncRelayCommand(cancelableExecute, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
|
||||
return Register(new AsyncRelayCommand(cancelableExecute, canExecute));
|
||||
}
|
||||
|
||||
private AsyncRelayCommand Register(AsyncRelayCommand command)
|
||||
{
|
||||
ReportException(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
private AsyncRelayCommand<T> Register<T>(AsyncRelayCommand<T> command)
|
||||
{
|
||||
ReportException(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
private void ReportException(IAsyncRelayCommand command)
|
||||
{
|
||||
command.PropertyChanged += (sender, args) =>
|
||||
{
|
||||
if (sender is IAsyncRelayCommand asyncRelayCommand)
|
||||
{
|
||||
if (args.PropertyName == nameof(AsyncRelayCommand.ExecutionTask))
|
||||
{
|
||||
if (asyncRelayCommand.ExecutionTask?.Exception is AggregateException exception)
|
||||
{
|
||||
Exception baseException = exception.GetBaseException();
|
||||
logger.LogError(EventIds.AsyncCommandException, baseException, "{name} Exception", nameof(asyncRelayCommand));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Binding;
|
||||
|
||||
namespace Snap.Hutao.Message;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
@@ -10,7 +11,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// 成就存档
|
||||
/// </summary>
|
||||
[Table("achievement_archives")]
|
||||
public class AchievementArchive
|
||||
public class AchievementArchive : ISelectable
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
@@ -10,7 +11,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// 用户
|
||||
/// </summary>
|
||||
[Table("users")]
|
||||
public class User
|
||||
public class User : ISelectable
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.0.28.0" />
|
||||
Version="1.0.29.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
@@ -24,9 +25,8 @@ internal class AchievementService : IAchievementService
|
||||
{
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly ILogger<AchievementService> logger;
|
||||
private readonly IMessenger messenger;
|
||||
private readonly DbCurrent<EntityArchive, Message.AchievementArchiveChangedMessage> dbCurrent;
|
||||
|
||||
private EntityArchive? currentArchive;
|
||||
private ObservableCollection<EntityArchive>? archiveCollection;
|
||||
|
||||
/// <summary>
|
||||
@@ -39,44 +39,15 @@ internal class AchievementService : IAchievementService
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
this.logger = logger;
|
||||
this.messenger = messenger;
|
||||
|
||||
dbCurrent = new(appDbContext, appDbContext.AchievementArchives, 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);
|
||||
}
|
||||
get => dbCurrent.Current;
|
||||
set => dbCurrent.Current = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -85,6 +56,47 @@ internal class AchievementService : IAchievementService
|
||||
return archiveCollection ??= new(appDbContext.AchievementArchives.ToList());
|
||||
}
|
||||
|
||||
/// <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 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<BindingAchievement> GetAchievements(EntityArchive archive, IList<MetadataAchievement> metadata)
|
||||
{
|
||||
@@ -139,19 +151,6 @@ internal class AchievementService : IAchievementService
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
@@ -170,34 +169,6 @@ internal class AchievementService : IAchievementService
|
||||
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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Navigation;
|
||||
|
||||
/// <summary>
|
||||
/// 导航消息接收器
|
||||
/// </summary>
|
||||
public interface INavigationRecipient
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步接收消息
|
||||
/// </summary>
|
||||
/// <param name="data">导航数据</param>
|
||||
/// <returns>接收处理结果是否成功</returns>
|
||||
Task<bool> ReceiveAsync(INavigationData data);
|
||||
}
|
||||
@@ -34,8 +34,8 @@ public sealed partial class AchievementImportDialog : ContentDialog
|
||||
/// </summary>
|
||||
public UIAF UIAF
|
||||
{
|
||||
get { return (UIAF)GetValue(UIAFProperty); }
|
||||
set { SetValue(UIAFProperty, value); }
|
||||
get => (UIAF)GetValue(UIAFProperty);
|
||||
set => SetValue(UIAFProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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.Logging;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Snap.Hutao.Control.Cancellable;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
@@ -24,12 +23,18 @@ public sealed partial class AchievementPage : CancellablePage
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
[SuppressMessage("", "VSTHRD100")]
|
||||
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if (e.Parameter is INavigationData extra)
|
||||
{
|
||||
if (extra.Data != null)
|
||||
{
|
||||
await ((INavigationRecipient)DataContext).ReceiveAsync(extra);
|
||||
}
|
||||
|
||||
extra.NotifyNavigationCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// 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.ViewModel;
|
||||
|
||||
namespace Snap.Hutao.View;
|
||||
|
||||
@@ -15,6 +15,7 @@ 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.Navigation;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -34,6 +35,7 @@ namespace Snap.Hutao.ViewModel;
|
||||
internal class AchievementViewModel
|
||||
: ObservableObject,
|
||||
ISupportCancellation,
|
||||
INavigationRecipient,
|
||||
IRecipient<AchievementArchiveChangedMessage>,
|
||||
IRecipient<MainWindowClosedMessage>
|
||||
{
|
||||
@@ -43,6 +45,8 @@ internal class AchievementViewModel
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly IPickerFactory pickerFactory;
|
||||
|
||||
private readonly TaskCompletionSource<bool> openUICompletionSource = new();
|
||||
|
||||
private AdvancedCollectionView? achievements;
|
||||
private IList<AchievementGoal>? achievementGoals;
|
||||
private AchievementGoal? selectedAchievementGoal;
|
||||
@@ -190,6 +194,21 @@ internal class AchievementViewModel
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> ReceiveAsync(INavigationData data)
|
||||
{
|
||||
if (await openUICompletionSource.Task)
|
||||
{
|
||||
if (data.Data is "InvokeByUri")
|
||||
{
|
||||
await ImportUIAFFromClipboardAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task HandleArchiveChangeAsync(Model.Entity.AchievementArchive? oldArchieve, Model.Entity.AchievementArchive? newArchieve)
|
||||
{
|
||||
if (oldArchieve != null && Achievements != null)
|
||||
@@ -209,7 +228,8 @@ internal class AchievementViewModel
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync(CancellationToken))
|
||||
bool metaInitialized = await metadataService.InitializeAsync(CancellationToken);
|
||||
if (metaInitialized)
|
||||
{
|
||||
AchievementGoals = await metadataService.GetAchievementGoalsAsync(CancellationToken);
|
||||
|
||||
@@ -222,6 +242,8 @@ internal class AchievementViewModel
|
||||
infoBarService.Warning("请创建或选择一个成就存档");
|
||||
}
|
||||
}
|
||||
|
||||
openUICompletionSource.TrySetResult(metaInitialized);
|
||||
}
|
||||
|
||||
private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive)
|
||||
@@ -291,8 +313,7 @@ internal class AchievementViewModel
|
||||
return;
|
||||
}
|
||||
|
||||
DataPackageView view = Clipboard.GetContent();
|
||||
string json = await view.GetTextAsync();
|
||||
string json = await Clipboard.GetContent().GetTextAsync();
|
||||
|
||||
UIAF? uiaf = null;
|
||||
try
|
||||
@@ -321,7 +342,7 @@ internal class AchievementViewModel
|
||||
await new ContentDialog2(App.Window!)
|
||||
{
|
||||
Title = "导入失败",
|
||||
Content = "剪贴板中的内容格式不正确",
|
||||
Content = "数据格式不正确",
|
||||
PrimaryButtonText = "确认",
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
}
|
||||
@@ -375,7 +396,7 @@ internal class AchievementViewModel
|
||||
await new ContentDialog2(App.Window!)
|
||||
{
|
||||
Title = "导入失败",
|
||||
Content = "文件中的内容格式不正确",
|
||||
Content = "数据格式不正确",
|
||||
PrimaryButtonText = "确认",
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
|
||||
@@ -27,11 +27,6 @@ public class Response : ISupportValidation
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IInfoBarService>().Error(ToString());
|
||||
}
|
||||
|
||||
if (ReturnCode != 0)
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IInfoBarService>().Warning(ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user