mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
make achievement great again
This commit is contained in:
@@ -322,6 +322,7 @@ dotnet_diagnostic.CA2227.severity = suggestion
|
||||
dotnet_diagnostic.CA2251.severity = suggestion
|
||||
|
||||
csharp_style_prefer_primary_constructors = false:none
|
||||
dotnet_diagnostic.SA1124.severity = none
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -1,16 +1,104 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Database.Abstraction;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using Snap.Hutao.UI.Xaml.Data;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
// The scope of the view follows the scope of the service provider.
|
||||
internal sealed class AdvancedDbCollectionView<TEntity> : AdvancedCollectionView<TEntity>
|
||||
where TEntity : class, IAdvancedCollectionViewItem, ISelectable
|
||||
{
|
||||
public AdvancedDbCollectionView(IList<TEntity> source)
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
private bool detached;
|
||||
|
||||
public AdvancedDbCollectionView(IList<TEntity> source, IServiceProvider serviceProvider)
|
||||
: base(source)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
detached = true;
|
||||
}
|
||||
|
||||
protected override void OnCurrentChangedOverride()
|
||||
{
|
||||
if (serviceProvider is null || detached)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TEntity? currentItem = CurrentItem;
|
||||
|
||||
foreach (TEntity item in Source)
|
||||
{
|
||||
item.IsSelected = ReferenceEquals(item, currentItem);
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
dbContext.Set<TEntity>().ExecuteUpdate(update => update.SetProperty(entity => entity.IsSelected, false));
|
||||
|
||||
if (currentItem is not null)
|
||||
{
|
||||
dbContext.Set<TEntity>().UpdateAndSave(currentItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The scope of the view follows the scope of the service provider.
|
||||
[SuppressMessage("", "SA1402")]
|
||||
internal sealed class AdvancedDbCollectionView<TEntityAccess, TEntity> : AdvancedCollectionView<TEntityAccess>
|
||||
where TEntityAccess : class, IEntityAccess<TEntity>, IAdvancedCollectionViewItem
|
||||
where TEntity : class, ISelectable
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
private bool detached;
|
||||
|
||||
public AdvancedDbCollectionView(IList<TEntityAccess> source, IServiceProvider serviceProvider)
|
||||
: base(source)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
detached = true;
|
||||
}
|
||||
|
||||
protected override void OnCurrentChangedOverride()
|
||||
{
|
||||
if (serviceProvider is null || detached)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TEntityAccess? currentItem = CurrentItem;
|
||||
|
||||
foreach (TEntityAccess item in Source)
|
||||
{
|
||||
item.Entity.IsSelected = ReferenceEquals(item, currentItem);
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
dbContext.Set<TEntity>().ExecuteUpdate(update => update.SetProperty(entity => entity.IsSelected, false));
|
||||
|
||||
if (currentItem is not null)
|
||||
{
|
||||
dbContext.Set<TEntity>().UpdateAndSave(currentItem.Entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,13 +64,13 @@ internal sealed class ObservableReorderableDbCollection<TEntity> : ObservableCol
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1402")]
|
||||
internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> : ObservableCollection<TEntityOnly>
|
||||
where TEntityOnly : class, IEntityAccess<TEntity>
|
||||
internal sealed class ObservableReorderableDbCollection<TEntityAccess, TEntity> : ObservableCollection<TEntityAccess>
|
||||
where TEntityAccess : class, IEntityAccess<TEntity>
|
||||
where TEntity : class, IReorderable
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public ObservableReorderableDbCollection(List<TEntityOnly> items, IServiceProvider serviceProvider)
|
||||
public ObservableReorderableDbCollection(List<TEntityAccess> items, IServiceProvider serviceProvider)
|
||||
: base(AdjustIndex(items.SortBy(x => x.Entity.Index)))
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
@@ -89,12 +89,12 @@ internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> :
|
||||
}
|
||||
}
|
||||
|
||||
private static List<TEntityOnly> AdjustIndex(List<TEntityOnly> list)
|
||||
private static List<TEntityAccess> AdjustIndex(List<TEntityAccess> list)
|
||||
{
|
||||
Span<TEntityOnly> span = CollectionsMarshal.AsSpan(list);
|
||||
Span<TEntityAccess> span = CollectionsMarshal.AsSpan(list);
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
ref readonly TEntityOnly item = ref span[i];
|
||||
ref readonly TEntityAccess item = ref span[i];
|
||||
item.Entity.Index = i;
|
||||
}
|
||||
|
||||
@@ -103,14 +103,14 @@ internal sealed class ObservableReorderableDbCollection<TEntityOnly, TEntity> :
|
||||
|
||||
private void OnReorder()
|
||||
{
|
||||
AdjustIndex((List<TEntityOnly>)Items);
|
||||
AdjustIndex((List<TEntityAccess>)Items);
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
DbSet<TEntity> dbSet = appDbContext.Set<TEntity>();
|
||||
foreach (ref readonly TEntityOnly item in CollectionsMarshal.AsSpan((List<TEntityOnly>)Items))
|
||||
foreach (ref readonly TEntityAccess item in CollectionsMarshal.AsSpan((List<TEntityAccess>)Items))
|
||||
{
|
||||
dbSet.UpdateAndSave(item.Entity);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Snap.Hutao.Model.Entity.Database;
|
||||
|
||||
namespace Snap.Hutao.Core.Database;
|
||||
|
||||
[Obsolete]
|
||||
[ConstructorGenerated]
|
||||
internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
||||
where TEntity : class, ISelectable
|
||||
@@ -63,6 +64,7 @@ internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[ConstructorGenerated]
|
||||
internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
where TEntityOnly : class, IEntityAccess<TEntity>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// 指示此特性附加的属性会在属性改变后会异步地设置的其他属性
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
|
||||
internal sealed class AlsoAsyncSetsAttribute : Attribute
|
||||
{
|
||||
public AlsoAsyncSetsAttribute(string propertyName)
|
||||
{
|
||||
}
|
||||
|
||||
public AlsoAsyncSetsAttribute(string propertyName1, string propertyName2)
|
||||
{
|
||||
}
|
||||
|
||||
public AlsoAsyncSetsAttribute(params string[] propertyNames)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// 指示此特性附加的属性会在属性改变后会设置的其他属性
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
|
||||
internal sealed class AlsoSetsAttribute : Attribute
|
||||
{
|
||||
public AlsoSetsAttribute(string propertyName)
|
||||
{
|
||||
}
|
||||
|
||||
public AlsoSetsAttribute(string propertyName1, string propertyName2)
|
||||
{
|
||||
}
|
||||
|
||||
public AlsoSetsAttribute(params string[] propertyNames)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -58,5 +58,7 @@ internal sealed class TempFileStream : Stream
|
||||
stream.Dispose();
|
||||
File.Delete(path);
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ internal static class TaskExtension
|
||||
return new(task);
|
||||
}
|
||||
|
||||
[Obsolete("SafeForget without logger is not recommended.")]
|
||||
public static async void SafeForget(this Task task)
|
||||
{
|
||||
try
|
||||
@@ -100,6 +101,7 @@ internal static class TaskExtension
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("SafeForget without logger is not recommended.")]
|
||||
public static async void SafeForget(this ValueTask task)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -78,23 +78,6 @@ internal static class ListExtension
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool RemoveFirstWhere<T>(this List<T> list, Func<T, bool> shouldRemovePredicate)
|
||||
{
|
||||
Span<T> span = CollectionsMarshal.AsSpan(list);
|
||||
ref T reference = ref MemoryMarshal.GetReference(span);
|
||||
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
if (shouldRemovePredicate.Invoke(Unsafe.Add(ref reference, i)))
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void RemoveLast<T>(this IList<T> collection)
|
||||
{
|
||||
collection.RemoveAt(collection.Count - 1);
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity;
|
||||
|
||||
namespace Snap.Hutao.Message;
|
||||
|
||||
/// <summary>
|
||||
/// 成就存档切换消息
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete]
|
||||
internal sealed class AchievementArchiveChangedMessage : ValueChangedMessage<AchievementArchive>
|
||||
{
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Core.Database.Abstraction;
|
||||
using Snap.Hutao.UI.Xaml.Data;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
@@ -12,7 +13,9 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// 成就存档
|
||||
/// </summary>
|
||||
[Table("achievement_archives")]
|
||||
internal sealed class AchievementArchive : ISelectable, IMappingFrom<AchievementArchive, string>
|
||||
internal sealed class AchievementArchive : ISelectable,
|
||||
IAdvancedCollectionViewItem,
|
||||
IMappingFrom<AchievementArchive, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
@@ -40,4 +43,12 @@ internal sealed class AchievementArchive : ISelectable, IMappingFrom<Achievement
|
||||
{
|
||||
return new() { Name = name };
|
||||
}
|
||||
|
||||
public object? GetPropertyValue(string name)
|
||||
{
|
||||
return name switch
|
||||
{
|
||||
_ => default!,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ internal static class AppDbServiceExtension
|
||||
return service.Execute(dbset => dbset.AddAndSave(entity));
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static ValueTask<int> AddAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -78,6 +79,7 @@ internal static class AppDbServiceExtension
|
||||
return service.Execute(dbset => dbset.AddRangeAndSave(entities));
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static ValueTask<int> AddRangeAsync<TEntity>(this IAppDbService<TEntity> service, IEnumerable<TEntity> entities, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -144,6 +146,7 @@ internal static class AppDbServiceExtension
|
||||
return service.Execute(dbset => dbset.UpdateAndSave(entity));
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static ValueTask<int> UpdateAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
@@ -162,6 +165,7 @@ internal static class AppDbServiceExtension
|
||||
return service.Execute(dbset => dbset.Where(predicate).ExecuteDelete());
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static ValueTask<int> DeleteAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.Achievement;
|
||||
using System.Collections.ObjectModel;
|
||||
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
|
||||
|
||||
namespace Snap.Hutao.Service.Achievement;
|
||||
@@ -18,32 +17,17 @@ namespace Snap.Hutao.Service.Achievement;
|
||||
[Injection(InjectAs.Scoped, typeof(IAchievementService))]
|
||||
internal sealed partial class AchievementService : IAchievementService
|
||||
{
|
||||
private readonly ScopedDbCurrent<AchievementArchive, Message.AchievementArchiveChangedMessage> dbCurrent;
|
||||
private readonly AchievementDbBulkOperation achievementDbBulkOperation;
|
||||
private readonly IAchievementDbService achievementDbService;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
private ObservableCollection<AchievementArchive>? archiveCollection;
|
||||
private AdvancedDbCollectionView<AchievementArchive>? archivesView;
|
||||
|
||||
public AchievementArchive? CurrentArchive
|
||||
public AdvancedDbCollectionView<AchievementArchive> Archives
|
||||
{
|
||||
get => dbCurrent.Current;
|
||||
set => dbCurrent.Current = value;
|
||||
}
|
||||
|
||||
public ObservableCollection<AchievementArchive> ArchiveCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (archiveCollection is null)
|
||||
{
|
||||
archiveCollection = achievementDbService.GetAchievementArchiveCollection();
|
||||
CurrentArchive = archiveCollection.SelectedOrDefault();
|
||||
}
|
||||
|
||||
return archiveCollection;
|
||||
}
|
||||
get => archivesView ??= new(achievementDbService.GetAchievementArchiveCollection(), serviceProvider);
|
||||
}
|
||||
|
||||
public List<AchievementView> GetAchievementViewList(AchievementArchive archive, AchievementServiceMetadataContext context)
|
||||
@@ -69,31 +53,27 @@ internal sealed partial class AchievementService : IAchievementService
|
||||
return ArchiveAddResultKind.InvalidName;
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(archiveCollection);
|
||||
ArgumentNullException.ThrowIfNull(archivesView);
|
||||
|
||||
if (archiveCollection.Any(a => a.Name == newArchive.Name))
|
||||
if (archivesView.SourceCollection.Any(a => a.Name == newArchive.Name))
|
||||
{
|
||||
return ArchiveAddResultKind.AlreadyExists;
|
||||
}
|
||||
|
||||
// Sync cache
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
archiveCollection.Add(newArchive);
|
||||
|
||||
// Sync database
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
CurrentArchive = newArchive;
|
||||
archivesView.Add(newArchive);
|
||||
archivesView.MoveCurrentTo(newArchive);
|
||||
|
||||
return ArchiveAddResultKind.Added;
|
||||
}
|
||||
|
||||
public async ValueTask RemoveArchiveAsync(AchievementArchive archive)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(archiveCollection);
|
||||
ArgumentNullException.ThrowIfNull(archivesView);
|
||||
|
||||
// Sync cache
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
archiveCollection.Remove(archive);
|
||||
archivesView.Remove(archive);
|
||||
|
||||
// Sync database
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
@@ -1,64 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.ViewModel.Achievement;
|
||||
using System.Collections.ObjectModel;
|
||||
using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
|
||||
|
||||
namespace Snap.Hutao.Service.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就服务抽象
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface IAchievementService
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前存档
|
||||
/// </summary>
|
||||
EntityArchive? CurrentArchive { get; set; }
|
||||
AdvancedDbCollectionView<EntityArchive> Archives { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取用于绑定的成就存档集合
|
||||
/// </summary>
|
||||
ObservableCollection<EntityArchive> ArchiveCollection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 异步导出到UIAF
|
||||
/// </summary>
|
||||
/// <param name="selectedArchive">存档</param>
|
||||
/// <returns>UIAF</returns>
|
||||
ValueTask<UIAF> ExportToUIAFAsync(EntityArchive selectedArchive);
|
||||
|
||||
List<AchievementView> GetAchievementViewList(EntityArchive archive, AchievementServiceMetadataContext context);
|
||||
|
||||
/// <summary>
|
||||
/// 异步导入UIAF数据
|
||||
/// </summary>
|
||||
/// <param name="archive">用户</param>
|
||||
/// <param name="list">UIAF数据</param>
|
||||
/// <param name="strategy">选项</param>
|
||||
/// <returns>导入结果</returns>
|
||||
ValueTask<ImportResult> ImportFromUIAFAsync(EntityArchive archive, List<UIAFItem> list, ImportStrategyKind strategy);
|
||||
|
||||
/// <summary>
|
||||
/// 异步移除存档
|
||||
/// </summary>
|
||||
/// <param name="archive">待移除的存档</param>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask RemoveArchiveAsync(EntityArchive archive);
|
||||
|
||||
/// <summary>
|
||||
/// 保存单个成就
|
||||
/// </summary>
|
||||
/// <param name="achievement">成就</param>
|
||||
void SaveAchievement(AchievementView achievement);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试添加存档
|
||||
/// </summary>
|
||||
/// <param name="newArchive">新存档</param>
|
||||
/// <returns>存档添加结果</returns>
|
||||
ValueTask<ArchiveAddResultKind> AddArchiveAsync(EntityArchive newArchive);
|
||||
}
|
||||
@@ -314,7 +314,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.1.0">
|
||||
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.1.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -53,7 +53,7 @@ internal class ScopedPage : Page
|
||||
TViewModel viewModel = pageScope.ServiceProvider.GetRequiredService<TViewModel>();
|
||||
using (viewModel.DisposeLock.Enter())
|
||||
{
|
||||
viewModel.IsViewDisposed = false;
|
||||
viewModel.Resurrect();
|
||||
viewModel.CancellationToken = viewCancellationTokenSource.Token;
|
||||
viewModel.DeferContentLoader = new DeferContentLoader(this);
|
||||
}
|
||||
@@ -106,10 +106,10 @@ internal class ScopedPage : Page
|
||||
viewCancellationTokenSource.Cancel();
|
||||
IViewModel viewModel = (IViewModel)DataContext;
|
||||
|
||||
// Wait to ensure viewmodel operation is completed
|
||||
using (viewModel.DisposeLock.Enter())
|
||||
{
|
||||
// Wait to ensure viewmodel operation is completed
|
||||
viewModel.IsViewDisposed = true;
|
||||
viewModel.Uninitialize();
|
||||
|
||||
// Dispose the scope
|
||||
pageScope.Dispose();
|
||||
|
||||
@@ -689,9 +689,9 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
|
||||
return;
|
||||
}
|
||||
|
||||
OnCurrentChangedOverride();
|
||||
CurrentChanged?.Invoke(this, default!);
|
||||
OnPropertyChanged(nameof(CurrentItem));
|
||||
OnCurrentChangedOverride();
|
||||
}
|
||||
|
||||
private void OnVectorChanged(IVectorChangedEventArgs e)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
Style="{ThemeResource DefaultButtonStyle}"
|
||||
mc:Ignorable="d">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Button.Resources>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Button.Resources>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Button.Resources>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
Style="{ThemeResource DefaultButtonStyle}"
|
||||
mc:Ignorable="d">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<Grid CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Grid Margin="12" ColumnSpacing="16">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<UserControl.Resources>
|
||||
|
||||
@@ -182,17 +182,17 @@
|
||||
x:Key="AchievementListViewItemStyle"
|
||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||
TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Margin" Value="0,4,8,0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid Visibility="{Binding IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Grid Visibility="{Binding SelectedArchive, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
|
||||
<Grid Visibility="{Binding Archives.CurrentItem, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
@@ -261,7 +261,7 @@
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Archives, Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"
|
||||
SelectedItem="{Binding Archives.CurrentItem, Mode=TwoWay}"
|
||||
Style="{ThemeResource CommandBarComboBoxStyle}"/>
|
||||
</shuxc:SizeRestrictedContentControl>
|
||||
</AppBarElementContainer>
|
||||
@@ -294,7 +294,6 @@
|
||||
Icon="{shuxm:FontIcon Glyph=}"
|
||||
Label="{shuxm:ResourceString Name=ViewPageAchievementExportLabel}"/>
|
||||
<AppBarSeparator/>
|
||||
|
||||
<AppBarToggleButton
|
||||
Command="{Binding SortUncompletedSwitchCommand}"
|
||||
Icon="{shuxm:FontIcon Glyph=}"
|
||||
@@ -322,7 +321,7 @@
|
||||
ItemContainerStyle="{StaticResource AchievementGoalListViewItemStyle}"
|
||||
ItemTemplate="{StaticResource AchievementGoalListTemplate}"
|
||||
ItemsSource="{Binding AchievementGoals}"
|
||||
SelectedItem="{Binding SelectedAchievementGoal, Mode=TwoWay}"
|
||||
SelectedItem="{Binding AchievementGoals.CurrentItem, Mode=TwoWay}"
|
||||
SelectionMode="Single">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:SelectedItemInViewBehavior/>
|
||||
@@ -347,7 +346,7 @@
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ItemTemplate="{StaticResource AchievementGoalGridTemplate}"
|
||||
ItemsSource="{Binding AchievementGoals}"
|
||||
SelectedItem="{Binding SelectedAchievementGoal, Mode=TwoWay}"
|
||||
SelectedItem="{Binding AchievementGoals.CurrentItem, Mode=TwoWay}"
|
||||
SelectionMode="Single">
|
||||
<GridView.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource DefaultGridViewItemStyle}" TargetType="GridViewItem">
|
||||
@@ -372,7 +371,7 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Visibility="{Binding SelectedArchive, Converter={StaticResource EmptyObjectToVisibilityRevertConverter}}">
|
||||
<Grid Visibility="{Binding Archives.CurrentItem, Converter={StaticResource EmptyObjectToVisibilityRevertConverter}}">
|
||||
<Border
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
mc:Ignorable="d">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<shuxc:ScopedPage.Resources>
|
||||
<shux:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
|
||||
@@ -257,7 +257,7 @@
|
||||
</Page.Resources>
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid Visibility="{Binding IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid x:Name="SettingPageGrid">
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<shuxc:ScopedPage.Resources>
|
||||
@@ -505,7 +505,7 @@
|
||||
<PivotItem Header="{shuxm:ResourceString Name=ViewSpiralAbyssHutaoStatistics}">
|
||||
<Grid DataContext="{Binding HutaoDatabaseViewModel}">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid.Resources>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
mc:Ignorable="d">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<Page.Resources>
|
||||
<shuxdc:VisibilityToObjectConverter
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
mc:Ignorable="d">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
mc:Ignorable="d">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid x:Name="DragableGrid">
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
</UserControl.Resources>
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<StackPanel
|
||||
Margin="0,0,0,-2"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<Grid Name="RootGrid" d:DataContext="{d:DesignInstance shvg:LaunchGameViewModel}">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
<shuxb:InvokeCommandOnLoadedBehavior Command="{Binding LoadCommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.UI.Xaml;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Abstraction;
|
||||
|
||||
[HighQuality]
|
||||
internal interface IViewModel : IPageScoped
|
||||
internal interface IViewModel : IPageScoped, IResurrectable
|
||||
{
|
||||
CancellationToken CancellationToken { get; set; }
|
||||
|
||||
@@ -15,4 +16,6 @@ internal interface IViewModel : IPageScoped
|
||||
IDeferContentLoader DeferContentLoader { get; set; }
|
||||
|
||||
bool IsViewDisposed { get; set; }
|
||||
|
||||
void Uninitialize();
|
||||
}
|
||||
@@ -27,15 +27,28 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
|
||||
public bool IsViewDisposed { get; set; }
|
||||
|
||||
protected TaskCompletionSource<bool> Initialization { get; } = new();
|
||||
protected TaskCompletionSource<bool> Initialization { get; set; } = new();
|
||||
|
||||
[Command("OpenUICommand")]
|
||||
protected virtual async Task OpenUIAsync()
|
||||
public void Resurrect()
|
||||
{
|
||||
IsViewDisposed = false;
|
||||
Initialization = new();
|
||||
}
|
||||
|
||||
public void Uninitialize()
|
||||
{
|
||||
UninitializeOverride();
|
||||
IsViewDisposed = true;
|
||||
DeferContentLoader = default!;
|
||||
}
|
||||
|
||||
[Command("LoadCommand")]
|
||||
protected virtual async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// ConfigureAwait(true) sets value on UI thread
|
||||
IsInitialized = await InitializeUIAsync().ConfigureAwait(true);
|
||||
IsInitialized = await InitializeOverrideAsync().ConfigureAwait(true);
|
||||
Initialization.TrySetResult(IsInitialized);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -43,12 +56,16 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual ValueTask<bool> InitializeUIAsync()
|
||||
protected virtual ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
|
||||
protected async ValueTask<IDisposable> EnterCriticalExecutionAsync()
|
||||
protected virtual void UninitializeOverride()
|
||||
{
|
||||
}
|
||||
|
||||
protected async ValueTask<IDisposable> EnterCriticalSectionAsync()
|
||||
{
|
||||
ThrowIfViewDisposed();
|
||||
IDisposable disposable = await DisposeLock.EnterAsync(CancellationToken).ConfigureAwait(false);
|
||||
@@ -100,17 +117,6 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool SetProperty<T>(ref T storage, T value, Func<T, ValueTask> changedAsyncCallback, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (SetProperty(ref storage, value, propertyName))
|
||||
{
|
||||
changedAsyncCallback(value).SafeForget();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void ThrowIfViewDisposed()
|
||||
|
||||
@@ -17,7 +17,7 @@ internal abstract partial class ViewModelSlim : ObservableObject
|
||||
|
||||
protected IServiceProvider ServiceProvider { get => serviceProvider; }
|
||||
|
||||
[Command("OpenUICommand")]
|
||||
[Command("LoadCommand")]
|
||||
protected virtual Task OpenUIAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -17,34 +17,34 @@ namespace Snap.Hutao.ViewModel.Achievement;
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementImporter
|
||||
{
|
||||
private readonly AchievementImporterDependencies dependencies;
|
||||
private readonly AchievementImporterScopeContext scopeContext;
|
||||
|
||||
public async ValueTask<bool> FromClipboardAsync()
|
||||
public async ValueTask<bool> FromClipboardAsync(AchievementViewModelScopeContext context)
|
||||
{
|
||||
if (dependencies.AchievementService.CurrentArchive is not { } archive)
|
||||
if (context.AchievementService.Archives.CurrentItem is not { } archive)
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
scopeContext.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await TryCatchGetUIAFFromClipboardAsync().ConfigureAwait(false) is not { } uiaf)
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
scopeContext.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
return await TryImportCoreAsync(context, archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async ValueTask<bool> FromFileAsync()
|
||||
public async ValueTask<bool> FromFileAsync(AchievementViewModelScopeContext context)
|
||||
{
|
||||
if (dependencies.AchievementService.CurrentArchive is not { } archive)
|
||||
if (context.AchievementService.Archives.CurrentItem is not { } archive)
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
scopeContext.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
return false;
|
||||
}
|
||||
|
||||
ValueResult<bool, ValueFile> pickerResult = dependencies.FileSystemPickerInteraction.PickFile(
|
||||
ValueResult<bool, ValueFile> pickerResult = scopeContext.FileSystemPickerInteraction.PickFile(
|
||||
SH.ServiceAchievementUIAFImportPickerTitile,
|
||||
[(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]);
|
||||
|
||||
@@ -53,39 +53,39 @@ internal sealed partial class AchievementImporter
|
||||
return false;
|
||||
}
|
||||
|
||||
ValueResult<bool, UIAF?> uiafResult = await file.DeserializeFromJsonAsync<UIAF>(dependencies.JsonSerializerOptions).ConfigureAwait(false);
|
||||
ValueResult<bool, UIAF?> uiafResult = await file.DeserializeFromJsonAsync<UIAF>(scopeContext.JsonSerializerOptions).ConfigureAwait(false);
|
||||
|
||||
if (!uiafResult.TryGetValue(out UIAF? uiaf))
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
scopeContext.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
return await TryImportCoreAsync(context, archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask<UIAF?> TryCatchGetUIAFFromClipboardAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await dependencies.ClipboardProvider.DeserializeFromJsonAsync<UIAF>().ConfigureAwait(false);
|
||||
return await scopeContext.ClipboardProvider.DeserializeFromJsonAsync<UIAF>().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
dependencies.InfoBarService.Error(ex, SH.ViewModelImportFromClipboardErrorTitle);
|
||||
scopeContext.InfoBarService.Error(ex, SH.ViewModelImportFromClipboardErrorTitle);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<bool> TryImportAsync(EntityAchievementArchive archive, UIAF uiaf)
|
||||
private async ValueTask<bool> TryImportCoreAsync(AchievementViewModelScopeContext context, EntityAchievementArchive archive, UIAF uiaf)
|
||||
{
|
||||
if (!uiaf.IsCurrentVersionSupported())
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
|
||||
scopeContext.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
AchievementImportDialog importDialog = await dependencies.ContentDialogFactory
|
||||
AchievementImportDialog importDialog = await scopeContext.ContentDialogFactory
|
||||
.CreateInstanceAsync<AchievementImportDialog>(uiaf).ConfigureAwait(false);
|
||||
(bool isOk, ImportStrategyKind strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -94,18 +94,18 @@ internal sealed partial class AchievementImporter
|
||||
return false;
|
||||
}
|
||||
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = await dependencies.ContentDialogFactory
|
||||
await scopeContext.TaskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = await scopeContext.ContentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ImportResult result;
|
||||
using (await dialog.BlockAsync(dependencies.TaskContext).ConfigureAwait(false))
|
||||
using (await dialog.BlockAsync(scopeContext.TaskContext).ConfigureAwait(false))
|
||||
{
|
||||
result = await dependencies.AchievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||
result = await context.AchievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
dependencies.InfoBarService.Success($"{result}");
|
||||
scopeContext.InfoBarService.Success($"{result}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,11 @@ namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementImporterDependencies
|
||||
internal sealed partial class AchievementImporterScopeContext
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IClipboardProvider clipboardProvider;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ITaskContext taskContext;
|
||||
@@ -27,8 +26,6 @@ internal sealed partial class AchievementImporterDependencies
|
||||
|
||||
public IContentDialogFactory ContentDialogFactory { get => contentDialogFactory; }
|
||||
|
||||
public IAchievementService AchievementService { get => achievementService; }
|
||||
|
||||
public IClipboardProvider ClipboardProvider { get => clipboardProvider; }
|
||||
|
||||
public IInfoBarService InfoBarService { get => infoBarService; }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
@@ -13,16 +15,12 @@ using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.UI.Xaml.Data;
|
||||
using Snap.Hutao.UI.Xaml.View.Dialog;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
|
||||
using MetadataAchievementGoal = Snap.Hutao.Model.Metadata.Achievement.AchievementGoal;
|
||||
using SortDescription = CommunityToolkit.WinUI.Collections.SortDescription;
|
||||
using SortDirection = CommunityToolkit.WinUI.Collections.SortDirection;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INavigationRecipient
|
||||
@@ -34,32 +32,31 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
private readonly SortDescription achievementDefaultSortDescription = new(nameof(AchievementView.Order), SortDirection.Ascending);
|
||||
private readonly SortDescription achievementGoalDefaultSortDescription = new(nameof(AchievementGoalView.Order), SortDirection.Ascending);
|
||||
|
||||
private readonly AchievementViewModelDependencies dependencies;
|
||||
private readonly AchievementViewModelScopeContext scopeContext;
|
||||
|
||||
private AdvancedCollectionView<AchievementView>? achievements;
|
||||
private AdvancedCollectionView<AchievementGoalView>? achievementGoals;
|
||||
private AchievementGoalView? selectedAchievementGoal;
|
||||
private ObservableCollection<EntityAchievementArchive>? archives;
|
||||
private EntityAchievementArchive? selectedArchive;
|
||||
private AdvancedDbCollectionView<EntityAchievementArchive>? archives;
|
||||
|
||||
private bool isUncompletedItemsFirst = true;
|
||||
private string searchText = string.Empty;
|
||||
private string? finishDescription;
|
||||
|
||||
public ObservableCollection<EntityAchievementArchive>? Archives
|
||||
public AdvancedDbCollectionView<EntityAchievementArchive>? Archives
|
||||
{
|
||||
get => archives;
|
||||
set => SetProperty(ref archives, value);
|
||||
}
|
||||
|
||||
public EntityAchievementArchive? SelectedArchive
|
||||
{
|
||||
get => selectedArchive;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedArchive, value))
|
||||
if (archives is not null)
|
||||
{
|
||||
dependencies.AchievementService.CurrentArchive = value;
|
||||
UpdateAchievementsAsync(value).SafeForget();
|
||||
archives.CurrentChanged -= OnCurrentArchiveChanged;
|
||||
}
|
||||
|
||||
SetProperty(ref archives, value);
|
||||
|
||||
if (value is not null)
|
||||
{
|
||||
value.CurrentChanged += OnCurrentArchiveChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,18 +70,18 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
public AdvancedCollectionView<AchievementGoalView>? AchievementGoals
|
||||
{
|
||||
get => achievementGoals;
|
||||
set => SetProperty(ref achievementGoals, value);
|
||||
}
|
||||
|
||||
public AchievementGoalView? SelectedAchievementGoal
|
||||
{
|
||||
get => selectedAchievementGoal;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedAchievementGoal, value))
|
||||
if (achievementGoals is not null)
|
||||
{
|
||||
SearchText = string.Empty;
|
||||
UpdateAchievementsFilterByGoal(value);
|
||||
achievementGoals.CurrentChanged -= OnCurrentAchievementGoalChanged;
|
||||
}
|
||||
|
||||
SetProperty(ref achievementGoals, value);
|
||||
|
||||
if (value is not null)
|
||||
{
|
||||
value.CurrentChanged += OnCurrentAchievementGoalChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,142 +119,165 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
if (!await dependencies.MetadataService.InitializeAsync().ConfigureAwait(false))
|
||||
if (!await scopeContext.MetadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<AchievementGoalView> sortedGoals;
|
||||
ObservableCollection<EntityAchievementArchive> archives;
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
List<MetadataAchievementGoal> goals = await dependencies.MetadataService
|
||||
List<MetadataAchievementGoal> goals = await scopeContext.MetadataService
|
||||
.GetAchievementGoalListAsync(CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
|
||||
archives = dependencies.AchievementService.ArchiveCollection;
|
||||
}
|
||||
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
await scopeContext.TaskContext.SwitchToMainThreadAsync();
|
||||
|
||||
AchievementGoals = new(sortedGoals, true);
|
||||
Archives = archives;
|
||||
SelectedArchive = dependencies.AchievementService.CurrentArchive;
|
||||
Archives = scopeContext.AchievementService.Archives;
|
||||
Archives.MoveCurrentTo(Archives.SourceCollection.SelectedOrDefault());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void UninitializeOverride()
|
||||
{
|
||||
Archives?.Detach();
|
||||
Archives = default;
|
||||
AchievementGoals = default;
|
||||
Achievements = default;
|
||||
}
|
||||
|
||||
[GeneratedRegex("\\d\\.\\d")]
|
||||
private static partial Regex VersionRegex();
|
||||
|
||||
private void OnCurrentArchiveChanged(object? sender, object? e)
|
||||
{
|
||||
UpdateAchievementsAsync(Archives?.CurrentItem).SafeForget(scopeContext.Logger);
|
||||
}
|
||||
|
||||
private void OnCurrentAchievementGoalChanged(object? sender, object? e)
|
||||
{
|
||||
SearchText = string.Empty;
|
||||
UpdateAchievementsFilterByGoal(AchievementGoals?.CurrentItem);
|
||||
}
|
||||
|
||||
[Command("AddArchiveCommand")]
|
||||
private async Task AddArchiveAsync()
|
||||
{
|
||||
if (Archives is not null)
|
||||
if (Archives is null)
|
||||
{
|
||||
AchievementArchiveCreateDialog dialog = await dependencies.ContentDialogFactory.CreateInstanceAsync<AchievementArchiveCreateDialog>().ConfigureAwait(false);
|
||||
(bool isOk, string name) = await dialog.GetInputAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ArchiveAddResultKind result = await dependencies.AchievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false);
|
||||
AchievementArchiveCreateDialog dialog = await scopeContext.ContentDialogFactory.CreateInstanceAsync<AchievementArchiveCreateDialog>().ConfigureAwait(false);
|
||||
(bool isOk, string name) = await dialog.GetInputAsync().ConfigureAwait(false);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case ArchiveAddResultKind.Added:
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
SelectedArchive = dependencies.AchievementService.CurrentArchive;
|
||||
dependencies.InfoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name));
|
||||
break;
|
||||
case ArchiveAddResultKind.InvalidName:
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName);
|
||||
break;
|
||||
case ArchiveAddResultKind.AlreadyExists:
|
||||
dependencies.InfoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name));
|
||||
break;
|
||||
default:
|
||||
throw HutaoException.NotSupported();
|
||||
}
|
||||
}
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (await scopeContext.AchievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false))
|
||||
{
|
||||
case ArchiveAddResultKind.Added:
|
||||
await scopeContext.TaskContext.SwitchToMainThreadAsync();
|
||||
scopeContext.InfoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name));
|
||||
break;
|
||||
case ArchiveAddResultKind.InvalidName:
|
||||
scopeContext.InfoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName);
|
||||
break;
|
||||
case ArchiveAddResultKind.AlreadyExists:
|
||||
scopeContext.InfoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name));
|
||||
break;
|
||||
default:
|
||||
throw HutaoException.NotSupported();
|
||||
}
|
||||
}
|
||||
|
||||
[Command("RemoveArchiveCommand")]
|
||||
private async Task RemoveArchiveAsync()
|
||||
{
|
||||
if (Archives is not null && SelectedArchive is not null)
|
||||
if (Archives is null || !(Archives.CurrentItem is { } current))
|
||||
{
|
||||
string title = SH.FormatViewModelAchievementRemoveArchiveTitle(SelectedArchive.Name);
|
||||
string content = SH.ViewModelAchievementRemoveArchiveContent;
|
||||
ContentDialogResult result = await dependencies.ContentDialogFactory
|
||||
.CreateForConfirmCancelAsync(title, content)
|
||||
.ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
ContentDialogResult result = await scopeContext.ContentDialogFactory
|
||||
.CreateForConfirmCancelAsync(
|
||||
SH.FormatViewModelAchievementRemoveArchiveTitle(current.Name),
|
||||
SH.ViewModelAchievementRemoveArchiveContent)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result is not ContentDialogResult.Primary)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
await dependencies.AchievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Re-select first archive
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
SelectedArchive = Archives.FirstOrDefault();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
await scopeContext.AchievementService.RemoveArchiveAsync(current).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await scopeContext.TaskContext.SwitchToMainThreadAsync();
|
||||
Archives.MoveCurrentToFirst();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ExportAsUIAFToFileCommand")]
|
||||
private async Task ExportAsUIAFToFileAsync()
|
||||
{
|
||||
if (SelectedArchive is not null && Achievements is not null)
|
||||
if (Archives?.CurrentItem is null || Achievements is null)
|
||||
{
|
||||
(bool isOk, ValueFile file) = dependencies.FileSystemPickerInteraction.SaveFile(
|
||||
SH.ViewModelAchievementUIAFExportPickerTitle,
|
||||
$"{dependencies.AchievementService.CurrentArchive?.Name}.json",
|
||||
[(SH.ViewModelAchievementExportFileType, "*.json")]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
UIAF uiaf = await dependencies.AchievementService.ExportToUIAFAsync(SelectedArchive).ConfigureAwait(false);
|
||||
if (await file.SerializeToJsonAsync(uiaf, dependencies.JsonSerializerOptions).ConfigureAwait(false))
|
||||
{
|
||||
dependencies.InfoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
|
||||
}
|
||||
}
|
||||
(bool isOk, ValueFile file) = scopeContext.FileSystemPickerInteraction.SaveFile(
|
||||
SH.ViewModelAchievementUIAFExportPickerTitle,
|
||||
$"{Archives.CurrentItem.Name}.json",
|
||||
[(SH.ViewModelAchievementExportFileType, "*.json")]);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UIAF uiaf = await scopeContext.AchievementService.ExportToUIAFAsync(Archives.CurrentItem).ConfigureAwait(false);
|
||||
if (await file.SerializeToJsonAsync(uiaf, scopeContext.JsonSerializerOptions).ConfigureAwait(false))
|
||||
{
|
||||
scopeContext.InfoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
scopeContext.InfoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ImportUIAFFromClipboardCommand")]
|
||||
private async Task ImportUIAFFromClipboardAsync()
|
||||
{
|
||||
if (await dependencies.AchievementImporter.FromClipboardAsync().ConfigureAwait(false))
|
||||
if (await scopeContext.AchievementImporter.FromClipboardAsync(scopeContext).ConfigureAwait(false))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dependencies.AchievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(dependencies.AchievementService.CurrentArchive).ConfigureAwait(false);
|
||||
await UpdateAchievementsAsync(Archives?.CurrentItem).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ImportUIAFFromFileCommand")]
|
||||
private async Task ImportUIAFFromFileAsync()
|
||||
{
|
||||
if (await dependencies.AchievementImporter.FromFileAsync().ConfigureAwait(false))
|
||||
if (await scopeContext.AchievementImporter.FromFileAsync(scopeContext).ConfigureAwait(false))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dependencies.AchievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(dependencies.AchievementService.CurrentArchive).ConfigureAwait(false);
|
||||
await UpdateAchievementsAsync(Archives?.CurrentItem).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,31 +289,32 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
return;
|
||||
}
|
||||
|
||||
AchievementServiceMetadataContext context = await dependencies.MetadataService
|
||||
AchievementServiceMetadataContext context = await scopeContext.MetadataService
|
||||
.GetContextAsync<AchievementServiceMetadataContext>(CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (TryGetAchievements(archive, context, out List<AchievementView>? combined))
|
||||
if (!TryGetAchievements(archive, context, out List<AchievementView>? combined))
|
||||
{
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
|
||||
Achievements = new(combined, true);
|
||||
UpdateAchievementsFinishPercent();
|
||||
UpdateAchievementsFilterByGoal(SelectedAchievementGoal);
|
||||
UpdateAchievementsSort();
|
||||
return;
|
||||
}
|
||||
|
||||
await scopeContext.TaskContext.SwitchToMainThreadAsync();
|
||||
Achievements = new(combined, true);
|
||||
AchievementFinishPercent.Update(this);
|
||||
UpdateAchievementsFilterByGoal(AchievementGoals?.CurrentItem);
|
||||
UpdateAchievementsSort();
|
||||
}
|
||||
|
||||
private bool TryGetAchievements(EntityAchievementArchive archive, AchievementServiceMetadataContext context, [NotNullWhen(true)] out List<AchievementView>? combined)
|
||||
{
|
||||
try
|
||||
{
|
||||
combined = dependencies.AchievementService.GetAchievementViewList(archive, context);
|
||||
combined = scopeContext.AchievementService.GetAchievementViewList(archive, context);
|
||||
return true;
|
||||
}
|
||||
catch (HutaoException ex)
|
||||
{
|
||||
dependencies.InfoBarService.Error(ex);
|
||||
scopeContext.InfoBarService.Error(ex);
|
||||
combined = default;
|
||||
return false;
|
||||
}
|
||||
@@ -340,45 +361,36 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
[Command("SearchAchievementCommand")]
|
||||
private void UpdateAchievementsFilterBySearch(string? search)
|
||||
{
|
||||
if (Achievements is not null)
|
||||
if (Achievements is null)
|
||||
{
|
||||
SetProperty(ref selectedAchievementGoal, null);
|
||||
|
||||
if (string.IsNullOrEmpty(search))
|
||||
{
|
||||
Achievements.Filter = default!;
|
||||
return;
|
||||
}
|
||||
|
||||
if (uint.TryParse(search, out uint achievementId))
|
||||
{
|
||||
Achievements.Filter = view => view.Inner.Id == achievementId;
|
||||
return;
|
||||
}
|
||||
|
||||
if (VersionRegex().IsMatch(search))
|
||||
{
|
||||
Achievements.Filter = view => view.Inner.Version == search;
|
||||
return;
|
||||
}
|
||||
|
||||
Achievements.Filter = view =>
|
||||
{
|
||||
return view.Inner.Title.Contains(search, StringComparison.CurrentCultureIgnoreCase)
|
||||
|| view.Inner.Description.Contains(search, StringComparison.CurrentCultureIgnoreCase);
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAchievementsFinishPercent()
|
||||
{
|
||||
// 保存成就状态时,需要保持当前选择的成就分类
|
||||
AchievementGoalView? currentSelectedAchievementGoal = SelectedAchievementGoal;
|
||||
AchievementGoals?.MoveCurrentTo(default);
|
||||
|
||||
// 仅 读取成就列表 与 保存成就状态 时需要刷新成就进度
|
||||
AchievementFinishPercent.Update(this);
|
||||
if (string.IsNullOrEmpty(search))
|
||||
{
|
||||
Achievements.Filter = default!;
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedAchievementGoal = currentSelectedAchievementGoal;
|
||||
if (uint.TryParse(search, out uint achievementId))
|
||||
{
|
||||
Achievements.Filter = view => view.Inner.Id == achievementId;
|
||||
return;
|
||||
}
|
||||
|
||||
if (VersionRegex().IsMatch(search))
|
||||
{
|
||||
Achievements.Filter = view => view.Inner.Version == search;
|
||||
return;
|
||||
}
|
||||
|
||||
Achievements.Filter = view =>
|
||||
{
|
||||
return view.Inner.Title.Contains(search, StringComparison.CurrentCultureIgnoreCase)
|
||||
|| view.Inner.Description.Contains(search, StringComparison.CurrentCultureIgnoreCase);
|
||||
};
|
||||
}
|
||||
|
||||
[Command("SaveAchievementCommand")]
|
||||
@@ -386,8 +398,8 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
if (achievement is not null)
|
||||
{
|
||||
dependencies.AchievementService.SaveAchievement(achievement);
|
||||
UpdateAchievementsFinishPercent();
|
||||
scopeContext.AchievementService.SaveAchievement(achievement);
|
||||
AchievementFinishPercent.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,19 +11,22 @@ namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementViewModelDependencies
|
||||
internal sealed partial class AchievementViewModelScopeContext
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly ILogger<AchievementViewModelScopeContext> logger;
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly AchievementImporter achievementImporter;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public IFileSystemPickerInteraction FileSystemPickerInteraction { get => fileSystemPickerInteraction; }
|
||||
|
||||
public ILogger<AchievementViewModelScopeContext> Logger { get => logger; }
|
||||
|
||||
public JsonSerializerOptions JsonSerializerOptions { get => jsonSerializerOptions; }
|
||||
|
||||
public IContentDialogFactory ContentDialogFactory { get => contentDialogFactory; }
|
||||
@@ -81,7 +81,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
||||
}
|
||||
}
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
|
||||
{
|
||||
@@ -124,7 +124,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
||||
try
|
||||
{
|
||||
ValueResult<RefreshResultKind, Summary?> summaryResult;
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
ContentDialog dialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyFetch)
|
||||
|
||||
@@ -59,7 +59,7 @@ internal sealed partial class HutaoDatabaseViewModel : Abstraction.ViewModel
|
||||
public Overview? Overview { get => overview; set => SetProperty(ref overview, value); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
protected override async Task InitializeAsync()
|
||||
{
|
||||
if (await hutaoCache.InitializeForSpiralAbyssViewAsync().ConfigureAwait(false))
|
||||
{
|
||||
|
||||
@@ -62,7 +62,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
|
||||
public ObservableCollection<StatisticsCultivateItem>? StatisticsItems { get => statisticsItems; set => SetProperty(ref statisticsItems, value); }
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
@@ -189,7 +189,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
return;
|
||||
}
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
ContentDialog dialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelCultivationRefreshInventoryProgress)
|
||||
|
||||
@@ -58,7 +58,7 @@ internal sealed partial class DailyNoteViewModel : Abstraction.ViewModel
|
||||
/// </summary>
|
||||
public ObservableCollection<DailyNoteEntry>? DailyNoteEntries { get => dailyNoteEntries; set => SetProperty(ref dailyNoteEntries, value); }
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
@@ -118,7 +118,7 @@ internal sealed partial class DailyNoteViewModel : Abstraction.ViewModel
|
||||
{
|
||||
if (entry is not null)
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
// ContentDialog must be created by main thread.
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
@@ -48,7 +48,7 @@ internal sealed partial class FeedbackViewModel : Abstraction.ViewModel
|
||||
|
||||
public IPInformation? IPInformation { get => ipInformation; private set => SetProperty(ref ipInformation, value); }
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
Response<IPInformation> resp = await hutaoInfrastructureClient.GetIPInformationAsync().ConfigureAwait(false);
|
||||
IPInformation info = resp.IsOk() ? resp.Data : IPInformation.Default;
|
||||
|
||||
@@ -95,7 +95,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
/// </summary>
|
||||
public HutaoCloudStatisticsViewModel HutaoCloudStatisticsViewModel { get => hutaoCloudStatisticsViewModel; }
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -104,7 +104,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
ArgumentNullException.ThrowIfNull(gachaLogService.ArchiveCollection);
|
||||
ObservableCollection<GachaArchive> archives = gachaLogService.ArchiveCollection;
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Archives = archives;
|
||||
@@ -173,7 +173,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
|
||||
try
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -278,7 +278,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
await gachaLogService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
await hutaoUserService.InitializeAsync().ConfigureAwait(false);
|
||||
await RefreshUidCollectionAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -95,7 +95,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
{
|
||||
try
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile();
|
||||
|
||||
@@ -170,7 +170,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
SelectedGamePathEntry = selectedEntry;
|
||||
}
|
||||
|
||||
protected override ValueTask<bool> InitializeUIAsync()
|
||||
protected override ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
ImmutableList<GamePathEntry> gamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
|
||||
SetGamePathEntriesAndSelectedGamePathEntry(gamePathEntries, entry);
|
||||
|
||||
@@ -167,7 +167,7 @@ internal sealed partial class GuideViewModel : Abstraction.ViewModel
|
||||
set => SetProperty(ref downloadSummaries, value);
|
||||
}
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
HutaoInfrastructureClient hutaoInfrastructureClient = serviceProvider.GetRequiredService<HutaoInfrastructureClient>();
|
||||
HutaoResponse<StaticResourceSizeInformation> response = await hutaoInfrastructureClient.GetStaticSizeAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -51,7 +51,7 @@ internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel
|
||||
|
||||
public List<CardReference>? Cards { get => cards; set => SetProperty(ref cards, value); }
|
||||
|
||||
protected override ValueTask<bool> InitializeUIAsync()
|
||||
protected override ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
InitializeDashboard();
|
||||
InitializeInGameAnnouncementAsync().SafeForget();
|
||||
|
||||
@@ -216,7 +216,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
protected override ValueTask<bool> InitializeUIAsync()
|
||||
protected override ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
CacheFolderView = new(taskContext, runtimeOptions.LocalCache);
|
||||
DataFolderView = new(taskContext, runtimeOptions.DataFolder);
|
||||
|
||||
@@ -67,7 +67,7 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
if (await spiralAbyssRecordService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
@@ -90,7 +90,7 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
|
||||
ObservableCollection<SpiralAbyssView>? collection = null;
|
||||
try
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
collection = await spiralAbyssRecordService
|
||||
.GetSpiralAbyssViewCollectionAsync(userAndUid)
|
||||
@@ -115,7 +115,7 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
|
||||
{
|
||||
try
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
await spiralAbyssRecordService
|
||||
.RefreshSpiralAbyssAsync(userAndUid)
|
||||
|
||||
@@ -56,7 +56,7 @@ internal sealed partial class TitleViewModel : Abstraction.ViewModel
|
||||
|
||||
public UpdateStatus? UpdateStatus { get => updateStatus; set => SetProperty(ref updateStatus, value); }
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
await DoCheckUpdateAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
|
||||
@@ -90,7 +90,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
|
||||
|
||||
public FrozenDictionary<string, SearchToken>? AvailableTokens { get => availableTokens; }
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
@@ -108,7 +108,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
|
||||
|
||||
await CombineComplexDataAsync(list, idMaterialMap).ConfigureAwait(false);
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Avatars = new(list, true);
|
||||
|
||||
@@ -49,7 +49,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
|
||||
/// </summary>
|
||||
public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); }
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
protected override async ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
@@ -66,7 +66,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
|
||||
|
||||
List<Monster> ordered = monsters.SortBy(m => m.RelationshipId.Value);
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Monsters = new(ordered, true);
|
||||
|
||||
@@ -90,7 +90,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
|
||||
public FrozenDictionary<string, SearchToken>? AvailableTokens { get => availableTokens; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
protected override async Task InitializeAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
@@ -107,7 +107,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
|
||||
|
||||
await CombineComplexDataAsync(list, idMaterialMap).ConfigureAwait(false);
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
using (await EnterCriticalSectionAsync().ConfigureAwait(false))
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user