diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/ObservableReorderableDbCollection.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/ObservableReorderableDbCollection.cs index 19a19dac..7caba81f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/ObservableReorderableDbCollection.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/ObservableReorderableDbCollection.cs @@ -1,25 +1,29 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using CommunityToolkit.WinUI.Collections; using Microsoft.EntityFrameworkCore; +using Snap.Hutao.Model; +using Snap.Hutao.Model.Entity.Database; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Runtime.InteropServices; namespace Snap.Hutao.Core.Database; -internal sealed class ObservableReorderableDbCollection : ObservableCollection - where T : class, IReorderable +internal sealed class ObservableReorderableDbCollection : ObservableCollection + where TEntity : class, IReorderable { - private readonly DbContext dbContext; - private bool previousChangeIsRemoved; + private readonly IServiceProvider serviceProvider; - public ObservableReorderableDbCollection(List items, DbContext dbContext) - : base(AdjustIndex(items)) + public ObservableReorderableDbCollection(List items, IServiceProvider serviceProvider) + : base(AdjustIndex(items.SortBy(x => x.Index))) { - this.dbContext = dbContext; + this.serviceProvider = serviceProvider; } + public IAdvancedCollectionView? View { get; set; } + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { base.OnCollectionChanged(e); @@ -27,26 +31,18 @@ internal sealed class ObservableReorderableDbCollection : ObservableCollectio switch (e.Action) { case NotifyCollectionChangedAction.Remove: - previousChangeIsRemoved = true; - break; case NotifyCollectionChangedAction.Add: - if (!previousChangeIsRemoved) - { - return; - } - OnReorder(); - previousChangeIsRemoved = false; break; } } - private static List AdjustIndex(List list) + private static List AdjustIndex(List list) { - Span span = CollectionsMarshal.AsSpan(list); + Span span = CollectionsMarshal.AsSpan(list); for (int i = 0; i < list.Count; i++) { - ref readonly T item = ref span[i]; + ref readonly TEntity item = ref span[i]; item.Index = i; } @@ -55,12 +51,79 @@ internal sealed class ObservableReorderableDbCollection : ObservableCollectio private void OnReorder() { - AdjustIndex((List)Items); - - DbSet dbSet = dbContext.Set(); - foreach (ref readonly T item in CollectionsMarshal.AsSpan((List)Items)) + using (View?.DeferRefresh()) { - dbSet.UpdateAndSave(item); + AdjustIndex((List)Items); + + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + DbSet dbSet = appDbContext.Set(); + foreach (ref readonly TEntity item in CollectionsMarshal.AsSpan((List)Items)) + { + dbSet.UpdateAndSave(item); + } + } + } + } +} + +[SuppressMessage("", "SA1402")] +internal sealed class ObservableReorderableDbCollection : ObservableCollection + where TEntityOnly : class, IEntityOnly + where TEntity : class, IReorderable +{ + private readonly IServiceProvider serviceProvider; + + public ObservableReorderableDbCollection(List items, IServiceProvider serviceProvider) + : base(AdjustIndex(items.SortBy(x => x.Entity.Index))) + { + this.serviceProvider = serviceProvider; + } + + public IAdvancedCollectionView? View { get; set; } + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + base.OnCollectionChanged(e); + + switch (e.Action) + { + case NotifyCollectionChangedAction.Remove: + case NotifyCollectionChangedAction.Add: + OnReorder(); + break; + } + } + + private static List AdjustIndex(List list) + { + Span span = CollectionsMarshal.AsSpan(list); + for (int i = 0; i < list.Count; i++) + { + ref readonly TEntityOnly item = ref span[i]; + item.Entity.Index = i; + } + + return list; + } + + private void OnReorder() + { + using (View?.DeferRefresh()) + { + AdjustIndex((List)Items); + + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + + DbSet dbSet = appDbContext.Set(); + foreach (ref readonly TEntityOnly item in CollectionsMarshal.AsSpan((List)Items)) + { + dbSet.UpdateAndSave(item.Entity); + } + } } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs index a9a99041..bc92c556 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs @@ -1,6 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Database; +using Snap.Hutao.Model; using System.Collections.ObjectModel; using System.Runtime.CompilerServices; using System.Text; @@ -113,6 +115,25 @@ internal static partial class EnumerableExtension return new ObservableCollection(source); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ObservableReorderableDbCollection ToObservableReorderableDbCollection(this IEnumerable source, IServiceProvider serviceProvider) + where TEntity : class, IReorderable + { + return source is List list + ? new ObservableReorderableDbCollection(list, serviceProvider) + : new ObservableReorderableDbCollection([.. source], serviceProvider); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ObservableReorderableDbCollection ToObservableReorderableDbCollection(this IEnumerable source, IServiceProvider serviceProvider) + where TEntityOnly : class, IEntityOnly + where TEntity : class, IReorderable + { + return source is List list + ? new ObservableReorderableDbCollection(list, serviceProvider) + : new ObservableReorderableDbCollection([.. source], serviceProvider); + } + /// /// Concatenates each element from the collection into single string. /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs index ff08a1a0..114dd3bd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/GameAccountService.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Database; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Model.Entity; @@ -18,9 +19,9 @@ internal sealed partial class GameAccountService : IGameAccountService private readonly IGameDbService gameDbService; private readonly ITaskContext taskContext; - private ObservableCollection? gameAccounts; + private ObservableReorderableDbCollection? gameAccounts; - public ObservableCollection GameAccountCollection + public ObservableReorderableDbCollection GameAccountCollection { get => gameAccounts ??= gameDbService.GetGameAccountCollection(); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/IGameAccountService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/IGameAccountService.cs index eaf88f1f..eb132170 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/IGameAccountService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Account/IGameAccountService.cs @@ -1,15 +1,15 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Primitive; -using System.Collections.ObjectModel; namespace Snap.Hutao.Service.Game.Account; internal interface IGameAccountService { - ObservableCollection GameAccountCollection { get; } + ObservableReorderableDbCollection GameAccountCollection { get; } void AttachGameAccountToUid(GameAccount gameAccount, string uid); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameDbService.cs index 220d5401..f346e0e5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameDbService.cs @@ -5,7 +5,6 @@ using Microsoft.EntityFrameworkCore; using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; -using System.Collections.ObjectModel; namespace Snap.Hutao.Service.Game; @@ -15,12 +14,12 @@ internal sealed partial class GameDbService : IGameDbService { private readonly IServiceProvider serviceProvider; - public ObservableCollection GetGameAccountCollection() + public ObservableReorderableDbCollection GetGameAccountCollection() { using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.GameAccounts.AsNoTracking().ToObservableCollection(); + return appDbContext.GameAccounts.AsNoTracking().ToObservableReorderableDbCollection(serviceProvider); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs index 6247aeed..83958a87 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameServiceFacade.cs @@ -1,13 +1,13 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Service.Game.Account; using Snap.Hutao.Service.Game.Configuration; using Snap.Hutao.Service.Game.Launching.Handler; using Snap.Hutao.Service.Game.PathAbstraction; -using System.Collections.ObjectModel; namespace Snap.Hutao.Service.Game; @@ -24,7 +24,7 @@ internal sealed partial class GameServiceFacade : IGameServiceFacade private readonly IGamePathService gamePathService; /// - public ObservableCollection GameAccountCollection + public ObservableReorderableDbCollection GameAccountCollection { get => gameAccountService.GameAccountCollection; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameDbService.cs index 0ee216f5..5550d018 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameDbService.cs @@ -1,8 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; -using System.Collections.ObjectModel; namespace Snap.Hutao.Service.Game; @@ -12,7 +12,7 @@ internal interface IGameDbService ValueTask RemoveGameAccountByIdAsync(Guid id); - ObservableCollection GetGameAccountCollection(); + ObservableReorderableDbCollection GetGameAccountCollection(); void UpdateGameAccount(GameAccount gameAccount); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameServiceFacade.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameServiceFacade.cs index b3e98cbc..ecb03421 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameServiceFacade.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameServiceFacade.cs @@ -1,10 +1,10 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Service.Game.Configuration; -using System.Collections.ObjectModel; namespace Snap.Hutao.Service.Game; @@ -17,7 +17,7 @@ internal interface IGameServiceFacade /// /// 游戏内账号集合 /// - ObservableCollection GameAccountCollection { get; } + ObservableReorderableDbCollection GameAccountCollection { get; } /// /// 将账号绑定到对应的Uid diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs index b34c8e67..4e6c80e9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserCollectionService.cs @@ -1,10 +1,12 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Database; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; +using EntityUser = Snap.Hutao.Model.Entity.User; namespace Snap.Hutao.Service.User; @@ -14,7 +16,7 @@ internal interface IUserCollectionService ValueTask> GetUserAndUidCollectionAsync(); - ValueTask> GetUserCollectionAsync(); + ValueTask> GetUserCollectionAsync(); UserGameRole? GetUserGameRoleByUid(string uid); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs index 9174bb80..011b0f40 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserService.cs @@ -1,10 +1,12 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Database; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; +using EntityUser = Snap.Hutao.Model.Entity.User; namespace Snap.Hutao.Service.User; @@ -32,7 +34,7 @@ internal interface IUserService /// 此操作不能取消 /// /// 准备完成的用户信息集合 - ValueTask> GetUserCollectionAsync(); + ValueTask> GetUserCollectionAsync(); /// /// 获取角色信息 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs index 76c6f72c..23d07405 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs @@ -9,6 +9,7 @@ using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; +using EntityUser = Snap.Hutao.Model.Entity.User; namespace Snap.Hutao.Service.User; @@ -18,13 +19,14 @@ internal sealed partial class UserCollectionService : IUserCollectionService, ID { private readonly ScopedDbCurrent dbCurrent; private readonly IUserInitializationService userInitializationService; + private readonly IServiceProvider serviceProvider; private readonly IUserDbService userDbService; private readonly ITaskContext taskContext; private readonly IMessenger messenger; private readonly SemaphoreSlim throttler = new(1); - private ObservableCollection? userCollection; + private ObservableReorderableDbCollection? userCollection; private Dictionary? midUserMap; private ObservableCollection? userAndUidCollection; @@ -36,7 +38,7 @@ internal sealed partial class UserCollectionService : IUserCollectionService, ID set => dbCurrent.Current = value; } - public async ValueTask> GetUserCollectionAsync() + public async ValueTask> GetUserCollectionAsync() { // Force run in background thread, otherwise will cause reentrance await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); @@ -62,7 +64,7 @@ internal sealed partial class UserCollectionService : IUserCollectionService, ID } } - userCollection = users.ToObservableCollection(); + userCollection = users.ToObservableReorderableDbCollection(serviceProvider); try { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index b42f23ab..4c3ffae1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Database; using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab; @@ -9,6 +10,7 @@ using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Web.Response; using System.Collections.ObjectModel; using BindingUser = Snap.Hutao.ViewModel.User.User; +using EntityUser = Snap.Hutao.Model.Entity.User; namespace Snap.Hutao.Service.User; @@ -41,7 +43,7 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe await userDbService.RemoveUsersAsync().ConfigureAwait(false); } - public ValueTask> GetUserCollectionAsync() + public ValueTask> GetUserCollectionAsync() { return userCollectionService.GetUserCollectionAsync(); } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml index b8134ef6..5561b78e 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml @@ -202,6 +202,8 @@ IsClickEnabled="True"/> diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml index 6a7fe707..19c24e07 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml @@ -261,6 +261,8 @@ diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index 8378e5f0..db52d5dc 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -5,6 +5,7 @@ using CommunityToolkit.WinUI.Collections; using Microsoft.Extensions.Caching.Memory; using Microsoft.UI.Windowing; using Snap.Hutao.Core; +using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Diagnostics.CodeAnalysis; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Model.Entity; @@ -18,7 +19,6 @@ using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher; using System.Collections.Immutable; -using System.Collections.ObjectModel; using System.IO; namespace Snap.Hutao.ViewModel.Game; @@ -241,6 +241,8 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView { await taskContext.SwitchToMainThreadAsync(); SelectedGameAccount = account; + + await UpdateGameAccountsViewAsync().ConfigureAwait(false); } } catch (UserdataCorruptedException ex) @@ -326,18 +328,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView GameResource = response.Data; } } - - async ValueTask UpdateGameAccountsViewAsync() - { - gameAccountFilter = new(SelectedScheme?.GetSchemeType()); - ObservableCollection accounts = gameService.GameAccountCollection; - - await taskContext.SwitchToMainThreadAsync(); - GameAccountsView = new(accounts, true) - { - Filter = gameAccountFilter.Filter, - }; - } } [Command("IdentifyMonitorsCommand")] @@ -363,4 +353,16 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView window.Close(); } } + + private async ValueTask UpdateGameAccountsViewAsync() + { + gameAccountFilter = new(SelectedScheme?.GetSchemeType()); + ObservableReorderableDbCollection accounts = gameService.GameAccountCollection; + + await taskContext.SwitchToMainThreadAsync(); + GameAccountsView = new(accounts, true) + { + Filter = gameAccountFilter.Filter, + }; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs index 67151fdb..cee9931e 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs @@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; using Snap.Hutao.Core; +using Snap.Hutao.Core.Database; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.IO.DataTransfer; using Snap.Hutao.Factory.ContentDialog; @@ -17,8 +18,8 @@ using Snap.Hutao.View.Page; using Snap.Hutao.Web.Hoyolab; using Snap.Hutao.Web.Hoyolab.Passport; using Snap.Hutao.Web.Response; -using System.Collections.ObjectModel; using System.Text; +using EntityUser = Snap.Hutao.Model.Entity.User; namespace Snap.Hutao.ViewModel.User; @@ -40,7 +41,7 @@ internal sealed partial class UserViewModel : ObservableObject private readonly IUserService userService; private User? selectedUser; - private ObservableCollection? users; + private ObservableReorderableDbCollection? users; /// /// 当前选择的用户信息 @@ -66,7 +67,7 @@ internal sealed partial class UserViewModel : ObservableObject /// /// 用户信息集合 /// - public ObservableCollection? Users { get => users; set => SetProperty(ref users, value); } + public ObservableReorderableDbCollection? Users { get => users; set => SetProperty(ref users, value); } /// /// 处理用户操作结果