Merge pull request #1366 from DGP-Studio/refine/1334

This commit is contained in:
DismissedLight
2024-02-06 11:16:58 +08:00
committed by GitHub
13 changed files with 963 additions and 61 deletions

View File

@@ -0,0 +1,768 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using CommunityToolkit.WinUI.Helpers;
using Microsoft.UI.Xaml.Data;
using Snap.Hutao.Core.ExceptionService;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;
using System.Runtime.CompilerServices;
using Windows.Foundation;
using Windows.Foundation.Collections;
using NotifyCollectionChangedAction = System.Collections.Specialized.NotifyCollectionChangedAction;
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<T>
where T : class
{
private readonly List<T> view;
private readonly ObservableCollection<SortDescription> sortDescriptions;
private readonly Dictionary<string, PropertyInfo?> sortProperties;
private readonly bool liveShapingEnabled;
private readonly HashSet<string?> observedFilterProperties = [];
private IList<T> source;
private Predicate<T>? filter;
private int deferCounter;
private WeakEventListener<AdvancedCollectionView<T>, object?, NotifyCollectionChangedEventArgs>? sourceWeakEventListener;
public AdvancedCollectionView()
: this(new List<T>(0))
{
}
public AdvancedCollectionView(IList<T> source, bool isLiveShaping = false)
{
liveShapingEnabled = isLiveShaping;
view = [];
sortDescriptions = [];
sortDescriptions.CollectionChanged += SortDescriptionsCollectionChanged;
sortProperties = [];
Source = source;
}
public event EventHandler<object>? CurrentChanged;
public event CurrentChangingEventHandler? CurrentChanging;
public event PropertyChangedEventHandler? PropertyChanged;
public event VectorChangedEventHandler<object>? VectorChanged;
public IList<T> Source
{
get => source;
[MemberNotNull(nameof(source))]
set
{
if (ReferenceEquals(source, value))
{
return;
}
if (source is not null)
{
DetachPropertyChangedHandler(source);
}
source = value;
AttachPropertyChangedHandler(source);
sourceWeakEventListener?.Detach();
if (source is INotifyCollectionChanged sourceNotifyCollectionChanged)
{
sourceWeakEventListener = new WeakEventListener<AdvancedCollectionView<T>, object?, NotifyCollectionChangedEventArgs>(this)
{
// Call the actual collection changed event
OnEventAction = (source, changed, arg3) => SourceNotifyCollectionChangedCollectionChanged(source, arg3),
// The source doesn't exist anymore
OnDetachAction = (listener) =>
{
ArgumentNullException.ThrowIfNull(sourceWeakEventListener);
sourceNotifyCollectionChanged.CollectionChanged -= sourceWeakEventListener.OnEvent;
},
};
sourceNotifyCollectionChanged.CollectionChanged += sourceWeakEventListener.OnEvent;
}
HandleSourceChanged();
OnPropertyChanged();
}
}
public int Count
{
get => view.Count;
}
public bool IsReadOnly
{
get => source is null || source.IsReadOnly;
}
public IObservableVector<object> CollectionGroups
{
get => default!;
}
public T? CurrentItem
{
get => CurrentPosition > -1 && CurrentPosition < view.Count ? view[CurrentPosition] : default;
set => MoveCurrentTo(value);
}
public int CurrentPosition { get; private set; }
public bool HasMoreItems
{
get => source is ISupportIncrementalLoading { HasMoreItems: true };
}
public bool IsCurrentAfterLast
{
get => CurrentPosition >= view.Count;
}
public bool IsCurrentBeforeFirst
{
get => CurrentPosition < 0;
}
public bool CanFilter
{
get => true;
}
public Predicate<T>? Filter
{
get => filter;
set
{
if (filter == value)
{
return;
}
filter = value;
HandleFilterChanged();
}
}
public bool CanSort
{
get => true;
}
public IList<SortDescription> SortDescriptions
{
get => sortDescriptions;
}
public IEnumerable<T> SourceCollection
{
get => source;
}
public IReadOnlyList<T> View
{
get => view;
}
public T this[int index]
{
get => view[index];
set => view[index] = value;
}
public void Refresh()
{
HandleSourceChanged();
}
public void RefreshFilter()
{
HandleFilterChanged();
}
public void RefreshSorting()
{
HandleSortChanged();
}
public IEnumerator<T> GetEnumerator()
{
return view.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return view.GetEnumerator();
}
public void Add(T item)
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Add(item);
}
public void Clear()
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Clear();
}
public bool Contains(T item)
{
return view.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
view.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Remove(item);
return true;
}
[SuppressMessage("", "SH007")]
public int IndexOf(T? item)
{
return view.IndexOf(item!);
}
public void Insert(int index, T item)
{
ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only.");
source.Insert(index, item);
}
public void RemoveAt(int index)
{
Remove(view[index]);
}
public bool MoveCurrentTo(T? item)
{
return (item is not null && item.Equals(CurrentItem)) || MoveCurrentToIndex(IndexOf(item));
}
public bool MoveCurrentToPosition(int index)
{
return MoveCurrentToIndex(index);
}
public bool MoveCurrentToFirst()
{
return MoveCurrentToIndex(0);
}
public bool MoveCurrentToLast()
{
return MoveCurrentToIndex(view.Count - 1);
}
public bool MoveCurrentToNext()
{
return MoveCurrentToIndex(CurrentPosition + 1);
}
public bool MoveCurrentToPrevious()
{
return MoveCurrentToIndex(CurrentPosition - 1);
}
public IAsyncOperation<LoadMoreItemsResult>? LoadMoreItemsAsync(uint count)
{
return (source as ISupportIncrementalLoading)?.LoadMoreItemsAsync(count);
}
public void ObserveFilterProperty(string propertyName)
{
observedFilterProperties.Add(propertyName);
}
public void ClearObservedFilterProperties()
{
observedFilterProperties.Clear();
}
public IDisposable DeferRefresh()
{
return new NotificationDeferrer(this);
}
int IComparer<T>.Compare(T? x, T? y)
{
if (sortProperties.Count <= 0)
{
Type listType = source.GetType();
Type? type;
if (listType.IsGenericType)
{
type = listType.GetGenericArguments()[0];
}
else
{
type = x?.GetType();
}
foreach (SortDescription sd in sortDescriptions)
{
if (!string.IsNullOrEmpty(sd.PropertyName))
{
sortProperties[sd.PropertyName] = type?.GetProperty(sd.PropertyName);
}
}
}
foreach (SortDescription sd in sortDescriptions)
{
T? cx, cy;
if (string.IsNullOrEmpty(sd.PropertyName))
{
cx = x;
cy = y;
}
else
{
PropertyInfo? pi = sortProperties[sd.PropertyName];
cx = (T?)pi?.GetValue(x);
cy = (T?)pi?.GetValue(y);
}
int cmp = sd.Comparer.Compare(cx, cy);
if (cmp is not 0)
{
return sd.Direction is SortDirection.Ascending ? +cmp : -cmp;
}
}
return 0;
}
internal void OnPropertyChanged([CallerMemberName] string propertyName = default!)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ItemOnPropertyChanged(object? item, PropertyChangedEventArgs e)
{
if (!liveShapingEnabled)
{
return;
}
ArgumentNullException.ThrowIfNull(item);
T typedItem = (T)item;
bool? filterResult = filter?.Invoke(typedItem);
if (filterResult.HasValue && observedFilterProperties.Contains(e.PropertyName))
{
int viewIndex = view.IndexOf(typedItem);
if (viewIndex != -1 && !filterResult.Value)
{
RemoveFromView(viewIndex, typedItem);
}
else if (viewIndex == -1 && filterResult.Value)
{
int index = source.IndexOf(typedItem);
HandleItemAdded(index, typedItem);
}
}
if ((filterResult ?? true) && SortDescriptions.Any(sd => sd.PropertyName == e.PropertyName))
{
int oldIndex = view.IndexOf(typedItem);
// Check if item is in view:
if (oldIndex < 0)
{
return;
}
view.RemoveAt(oldIndex);
int targetIndex = view.BinarySearch(typedItem, this);
if (targetIndex < 0)
{
targetIndex = ~targetIndex;
}
// Only trigger expensive UI updates if the index really changed:
if (targetIndex != oldIndex)
{
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, oldIndex, typedItem));
view.Insert(targetIndex, typedItem);
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, targetIndex, typedItem));
}
else
{
view.Insert(targetIndex, typedItem);
}
}
else if (string.IsNullOrEmpty(e.PropertyName))
{
HandleSourceChanged();
}
}
private void AttachPropertyChangedHandler(IEnumerable items)
{
if (!liveShapingEnabled || items is null)
{
return;
}
foreach (object item in items)
{
if (item is INotifyPropertyChanged notifyPropertyChanged)
{
notifyPropertyChanged.PropertyChanged += ItemOnPropertyChanged;
}
}
}
private void DetachPropertyChangedHandler(IEnumerable items)
{
if (!liveShapingEnabled || items is null)
{
return;
}
foreach (object item in items)
{
if (item is INotifyPropertyChanged notifyPropertyChanged)
{
notifyPropertyChanged.PropertyChanged -= ItemOnPropertyChanged;
}
}
}
private void HandleSortChanged()
{
sortProperties.Clear();
view.Sort(this);
sortProperties.Clear();
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
}
private void HandleFilterChanged()
{
if (filter is not null)
{
for (int index = 0; index < view.Count; index++)
{
T item = view[index];
if (filter(item))
{
continue;
}
RemoveFromView(index, item);
index--;
}
}
HashSet<T> viewHash = new(view);
int viewIndex = 0;
for (int index = 0; index < source.Count; index++)
{
T item = source[index];
if (viewHash.Contains(item))
{
viewIndex++;
continue;
}
if (HandleItemAdded(index, item, viewIndex))
{
viewIndex++;
}
}
}
private void HandleSourceChanged()
{
sortProperties.Clear();
T? currentItem = CurrentItem;
view.Clear();
foreach (T item in Source)
{
if (filter is not null && !filter(item))
{
continue;
}
if (sortDescriptions.Count > 0)
{
int targetIndex = view.BinarySearch(item, this);
if (targetIndex < 0)
{
targetIndex = ~targetIndex;
}
view.Insert(targetIndex, item);
}
else
{
view.Add(item);
}
}
sortProperties.Clear();
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
MoveCurrentTo(currentItem);
}
private void SourceNotifyCollectionChangedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
ArgumentNullException.ThrowIfNull(e.NewItems);
AttachPropertyChangedHandler(e.NewItems);
if (deferCounter <= 0)
{
if (e.NewItems?.Count == 1)
{
object? newItem = e.NewItems[0];
ArgumentNullException.ThrowIfNull(newItem);
HandleItemAdded(e.NewStartingIndex, (T)newItem);
}
else
{
HandleSourceChanged();
}
}
break;
case NotifyCollectionChangedAction.Remove:
ArgumentNullException.ThrowIfNull(e.OldItems);
DetachPropertyChangedHandler(e.OldItems);
if (deferCounter <= 0)
{
if (e.OldItems?.Count == 1)
{
object? oldItem = e.OldItems[0];
ArgumentNullException.ThrowIfNull(oldItem);
HandleItemRemoved(e.OldStartingIndex, (T)oldItem);
}
else
{
HandleSourceChanged();
}
}
break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
if (deferCounter <= 0)
{
HandleSourceChanged();
}
break;
}
}
private bool HandleItemAdded(int newStartingIndex, T newItem, int? viewIndex = null)
{
if (filter is not null && !filter(newItem))
{
return false;
}
int newViewIndex = view.Count;
if (sortDescriptions.Count > 0)
{
sortProperties.Clear();
newViewIndex = view.BinarySearch(newItem, this);
if (newViewIndex < 0)
{
newViewIndex = ~newViewIndex;
}
}
else if (filter is not null)
{
if (source is null)
{
HandleSourceChanged();
return false;
}
if (newStartingIndex == 0 || view.Count == 0)
{
newViewIndex = 0;
}
else if (newStartingIndex == source.Count - 1)
{
newViewIndex = view.Count;
}
else if (viewIndex.HasValue)
{
newViewIndex = viewIndex.Value;
}
else
{
for (int i = 0, j = 0; i < source.Count; i++)
{
if (i == newStartingIndex)
{
newViewIndex = j;
break;
}
if (Equals(view[j], source[i]))
{
j++;
}
}
}
}
view.Insert(newViewIndex, newItem);
if (newViewIndex <= CurrentPosition)
{
CurrentPosition++;
}
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, newViewIndex, newItem));
return true;
}
private void HandleItemRemoved(int oldStartingIndex, T oldItem)
{
if (filter is not null && !filter(oldItem))
{
return;
}
if (oldStartingIndex < 0 || oldStartingIndex >= view.Count || !Equals(view[oldStartingIndex], oldItem))
{
oldStartingIndex = view.IndexOf(oldItem);
}
if (oldStartingIndex < 0)
{
return;
}
RemoveFromView(oldStartingIndex, oldItem);
}
private void RemoveFromView(int itemIndex, T item)
{
view.RemoveAt(itemIndex);
if (itemIndex <= CurrentPosition)
{
CurrentPosition--;
}
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, itemIndex, item));
}
private void SortDescriptionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (deferCounter > 0)
{
return;
}
HandleSortChanged();
}
private bool MoveCurrentToIndex(int i)
{
if (i < -1 || i >= view.Count)
{
return false;
}
if (i == CurrentPosition)
{
return false;
}
CurrentChangingEventArgs e = new();
OnCurrentChanging(e);
if (e.Cancel)
{
return false;
}
CurrentPosition = i;
OnCurrentChanged(default!);
return true;
}
private void OnCurrentChanging(CurrentChangingEventArgs e)
{
if (deferCounter > 0)
{
return;
}
CurrentChanging?.Invoke(this, e);
}
private void OnCurrentChanged(object e)
{
if (deferCounter > 0)
{
return;
}
CurrentChanged?.Invoke(this, e);
OnPropertyChanged(nameof(CurrentItem));
}
private void OnVectorChanged(IVectorChangedEventArgs e)
{
if (deferCounter > 0)
{
return;
}
VectorChanged?.Invoke(this, e);
OnPropertyChanged(nameof(Count));
}
internal sealed class NotificationDeferrer : IDisposable
{
private readonly AdvancedCollectionView<T> advancedCollectionView;
private readonly T? currentItem;
public NotificationDeferrer(AdvancedCollectionView<T> acvs)
{
advancedCollectionView = acvs;
currentItem = advancedCollectionView.CurrentItem;
advancedCollectionView.deferCounter++;
}
public void Dispose()
{
advancedCollectionView.MoveCurrentTo(currentItem);
advancedCollectionView.deferCounter--;
advancedCollectionView.Refresh();
}
}
}

View File

@@ -0,0 +1,105 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Microsoft.UI.Xaml.Data;
using System.Collections;
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
internal interface IAdvancedCollectionView<T> : ICollectionView, IEnumerable
where T : class
{
bool CanFilter { get; }
bool CanSort { get; }
object? ICollectionView.CurrentItem
{
get => CurrentItem;
}
new T? CurrentItem { get; }
Predicate<T>? Filter { get; set; }
IList<SortDescription> SortDescriptions { get; }
IEnumerable<T> SourceCollection { get; }
object IList<object>.this[int index]
{
get => this[index];
set => this[index] = (T)value;
}
new T this[int index] { get; set; }
void ICollection<object>.Add(object item)
{
Add((T)item);
}
void Add(T item);
void ClearObservedFilterProperties();
bool ICollection<object>.Contains(object item)
{
return Contains((T)item);
}
bool Contains(T item);
void ICollection<object>.CopyTo(object[] array, int arrayIndex)
{
CopyTo((T[])array, arrayIndex);
}
void CopyTo(T[] array, int arrayIndex);
IDisposable DeferRefresh();
IEnumerator<object> IEnumerable<object>.GetEnumerator()
{
return GetEnumerator();
}
new IEnumerator<T> GetEnumerator();
int IList<object>.IndexOf(object item)
{
return IndexOf((T)item);
}
int IndexOf(T item);
void IList<object>.Insert(int index, object item)
{
Insert(index, (T)item);
}
void Insert(int index, T item);
bool ICollectionView.MoveCurrentTo(object item)
{
return MoveCurrentTo((T)item);
}
bool MoveCurrentTo(T item);
void ObserveFilterProperty(string propertyName);
void Refresh();
void RefreshFilter();
void RefreshSorting();
bool ICollection<object>.Remove(object item)
{
return Remove((T)item);
}
bool Remove(T item);
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Windows.Foundation.Collections;
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
internal sealed class VectorChangedEventArgs : IVectorChangedEventArgs
{
public VectorChangedEventArgs(CollectionChange cc, int index = -1, object item = null!)
{
CollectionChange = cc;
Index = (uint)index;
}
public CollectionChange CollectionChange { get; }
public uint Index { get; }
}

View File

@@ -73,6 +73,15 @@ internal static class ThrowHelper
throw new NotSupportedException(message);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void NotSupportedIf(bool condition, string message)
{
if (condition)
{
throw new NotSupportedException(message);
}
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default)

View File

@@ -1,8 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Factory.ContentDialog;
@@ -18,6 +18,8 @@ using System.Text.RegularExpressions;
using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
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;
@@ -41,7 +43,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
private readonly JsonSerializerOptions options;
private readonly ITaskContext taskContext;
private AdvancedCollectionView? achievements;
private AdvancedCollectionView<AchievementView>? achievements;
private List<AchievementGoalView>? achievementGoals;
private AchievementGoalView? selectedAchievementGoal;
private ObservableCollection<EntityAchievementArchive>? archives;
@@ -83,7 +85,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
/// <summary>
/// 成就视图
/// </summary>
public AdvancedCollectionView? Achievements
public AdvancedCollectionView<AchievementView>? Achievements
{
get => achievements;
set => SetProperty(ref achievements, value);
@@ -340,17 +342,19 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
[Command("SortUncompletedSwitchCommand")]
private void UpdateAchievementsSort()
{
if (Achievements is not null)
if (Achievements is null)
{
if (IsUncompletedItemsFirst)
{
Achievements.SortDescriptions.Add(uncompletedItemsFirstSortDescription);
Achievements.SortDescriptions.Add(completionTimeSortDescription);
}
else
{
Achievements.SortDescriptions.Clear();
}
return;
}
if (IsUncompletedItemsFirst)
{
Achievements.SortDescriptions.Add(uncompletedItemsFirstSortDescription);
Achievements.SortDescriptions.Add(completionTimeSortDescription);
}
else
{
Achievements.SortDescriptions.Clear();
}
}
@@ -365,7 +369,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
else
{
Model.Primitive.AchievementGoalId goalId = goal.Id;
Achievements.Filter = (object o) => o is AchievementView view && view.Inner.Goal == goalId;
Achievements.Filter = (AchievementView view) => view.Inner.Goal == goalId;
}
}
}
@@ -381,19 +385,18 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{
if (uint.TryParse(search, out uint achievementId))
{
Achievements.Filter = obj => ((AchievementView)obj).Inner.Id == achievementId;
Achievements.Filter = view => view.Inner.Id == achievementId;
return;
}
if (VersionRegex().IsMatch(search))
{
Achievements.Filter = obj => ((AchievementView)obj).Inner.Version == search;
Achievements.Filter = view => view.Inner.Version == search;
return;
}
Achievements.Filter = obj =>
Achievements.Filter = view =>
{
AchievementView view = (AchievementView)obj;
return view.Inner.Title.Contains(search, StringComparison.CurrentCultureIgnoreCase)
|| view.Inner.Description.Contains(search, StringComparison.CurrentCultureIgnoreCase);
};

View File

@@ -15,13 +15,13 @@ internal sealed class GameAccountFilter
this.type = type;
}
public bool Filter(object? item)
public bool Filter(GameAccount? item)
{
if (type is null)
{
return true;
}
return item is GameAccount account && account.Type == type;
return item is not null && item.Type == type;
}
}

View File

@@ -1,9 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.UI.Windowing;
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics.CodeAnalysis;
@@ -50,7 +50,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
private readonly AppOptions appOptions;
private LaunchScheme? selectedScheme;
private AdvancedCollectionView? gameAccountsView;
private AdvancedCollectionView<GameAccount>? gameAccountsView;
private GameAccount? selectedGameAccount;
private GameResource? gameResource;
private bool gamePathSelectedAndValid;
@@ -75,7 +75,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
set => SetSelectedSchemeAsync(value).SafeForget();
}
public AdvancedCollectionView? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
public AdvancedCollectionView<GameAccount>? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
@@ -130,9 +130,9 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
ArgumentNullException.ThrowIfNull(GameAccountsView);
// Exists in the source collection
if (GameAccountsView.SourceCollection.Cast<GameAccount>().FirstOrDefault(g => g.AttachUid == uid) is { } sourceAccount)
if (GameAccountsView.SourceCollection.FirstOrDefault(g => g.AttachUid == uid) is { } sourceAccount)
{
SelectedGameAccount = GameAccountsView.Cast<GameAccount>().FirstOrDefault(g => g.AttachUid == uid);
SelectedGameAccount = GameAccountsView.View.FirstOrDefault(g => g.AttachUid == uid);
// But not exists in the view for current scheme
if (SelectedGameAccount is null)
@@ -241,8 +241,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
{
await taskContext.SwitchToMainThreadAsync();
SelectedGameAccount = account;
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
}
}
catch (UserdataCorruptedException ex)
@@ -328,6 +326,18 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
GameResource = response.Data;
}
}
async ValueTask UpdateGameAccountsViewAsync()
{
gameAccountFilter = new(SelectedScheme?.GetSchemeType());
ObservableReorderableDbCollection<GameAccount> accounts = gameService.GameAccountCollection;
await taskContext.SwitchToMainThreadAsync();
GameAccountsView = new(accounts, true)
{
Filter = gameAccountFilter.Filter,
};
}
}
[Command("IdentifyMonitorsCommand")]
@@ -353,16 +363,4 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
window.Close();
}
}
private async ValueTask UpdateGameAccountsViewAsync()
{
gameAccountFilter = new(SelectedScheme?.GetSchemeType());
ObservableReorderableDbCollection<GameAccount> accounts = gameService.GameAccountCollection;
await taskContext.SwitchToMainThreadAsync();
GameAccountsView = new(accounts, true)
{
Filter = gameAccountFilter.Filter,
};
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Game;
@@ -27,13 +27,13 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
private readonly IGameServiceFacade gameService;
private readonly ITaskContext taskContext;
private AdvancedCollectionView? gameAccountsView;
private AdvancedCollectionView<GameAccount>? gameAccountsView;
private GameAccount? selectedGameAccount;
private GameAccountFilter? gameAccountFilter;
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
public AdvancedCollectionView? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
public AdvancedCollectionView<GameAccount>? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
/// <summary>
/// 选中的账号

View File

@@ -17,9 +17,9 @@ internal static class AvatarFilter
/// </summary>
/// <param name="input">输入</param>
/// <returns>筛选操作</returns>
public static Predicate<object> Compile(string input)
public static Predicate<Avatar> Compile(string input)
{
return (object o) => o is Avatar avatar && DoFilter(input, avatar);
return (Avatar avatar) => DoFilter(input, avatar);
}
private static bool DoFilter(string input, Avatar avatar)

View File

@@ -17,9 +17,9 @@ internal static class WeaponFilter
/// </summary>
/// <param name="input">输入</param>
/// <returns>筛选操作</returns>
public static Predicate<object> Compile(string input)
public static Predicate<Weapon> Compile(string input)
{
return (object o) => o is Weapon weapon && DoFilter(input, weapon);
return (Weapon weapon) => DoFilter(input, weapon);
}
private static bool DoFilter(string input, Weapon weapon)

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Entity.Primitive;
@@ -43,7 +43,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
private readonly CalculateClient calculateClient;
private readonly IUserService userService;
private AdvancedCollectionView? avatars;
private AdvancedCollectionView<Avatar>? avatars;
private Avatar? selected;
private string? filterText;
private BaseValueInfo? baseValueInfo;
@@ -53,7 +53,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
/// <summary>
/// 角色列表
/// </summary>
public AdvancedCollectionView? Avatars { get => avatars; set => SetProperty(ref avatars, value); }
public AdvancedCollectionView<Avatar>? Avatars { get => avatars; set => SetProperty(ref avatars, value); }
/// <summary>
/// 选中的角色
@@ -99,8 +99,8 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
await CombineComplexDataAsync(list, idMaterialMap).ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
Avatars = new AdvancedCollectionView(list, true);
Selected = Avatars.Cast<Avatar>().FirstOrDefault();
Avatars = new(list, true);
Selected = Avatars.View.ElementAtOrDefault(0);
return true;
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
@@ -20,7 +20,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
private readonly IMetadataService metadataService;
private readonly ITaskContext taskContext;
private AdvancedCollectionView? monsters;
private AdvancedCollectionView<Monster>? monsters;
private Monster? selected;
private BaseValueInfo? baseValueInfo;
private Dictionary<Level, Dictionary<GrowCurveType, float>>? levelMonsterCurveMap;
@@ -28,7 +28,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
/// <summary>
/// 角色列表
/// </summary>
public AdvancedCollectionView? Monsters { get => monsters; set => SetProperty(ref monsters, value); }
public AdvancedCollectionView<Monster>? Monsters { get => monsters; set => SetProperty(ref monsters, value); }
/// <summary>
/// 选中的角色
@@ -65,8 +65,8 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
List<Monster> ordered = monsters.SortBy(m => m.RelationshipId.Value);
await taskContext.SwitchToMainThreadAsync();
Monsters = new AdvancedCollectionView(ordered, true);
Selected = Monsters.Cast<Monster>().FirstOrDefault();
Monsters = new(ordered, true);
Selected = Monsters.View.ElementAtOrDefault(0);
return true;
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Entity.Primitive;
@@ -40,7 +40,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
private readonly IInfoBarService infoBarService;
private readonly IUserService userService;
private AdvancedCollectionView? weapons;
private AdvancedCollectionView<Weapon>? weapons;
private Weapon? selected;
private string? filterText;
private BaseValueInfo? baseValueInfo;
@@ -50,7 +50,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
/// <summary>
/// 角色列表
/// </summary>
public AdvancedCollectionView? Weapons { get => weapons; set => SetProperty(ref weapons, value); }
public AdvancedCollectionView<Weapon>? Weapons { get => weapons; set => SetProperty(ref weapons, value); }
/// <summary>
/// 选中的角色
@@ -96,8 +96,8 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
await taskContext.SwitchToMainThreadAsync();
Weapons = new AdvancedCollectionView(list, true);
Selected = Weapons.Cast<Weapon>().FirstOrDefault();
Weapons = new(list, true);
Selected = Weapons.View.ElementAtOrDefault(0);
}
}