diff --git a/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs b/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs index 06db70e8..d3eac6c9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs @@ -41,12 +41,14 @@ internal class ScopedPage : Page /// 应当在 InitializeComponent() 前调用 /// /// 视图模型类型 - protected void InitializeWith() + protected TViewModel InitializeWith() where TViewModel : class, IViewModel { IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService(); viewModel.CancellationToken = viewCancellationTokenSource.Token; DataContext = viewModel; + + return (TViewModel)viewModel; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml index 1bcfbd91..8be405da 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml @@ -73,6 +73,13 @@ + + + + @@ -266,21 +273,17 @@ - - - - - - + Text="{Binding FilterText, Mode=TwoWay}"/> public WikiAvatarPage() { - InitializeWith(); + WikiAvatarViewModel viewModel = InitializeWith(); InitializeComponent(); + + viewModel.Initialize(new AutoSuggestBoxAccessor(AvatarSuggestBox)); + } + + private class AutoSuggestBoxAccessor : IAutoSuggestBoxAccessor + { + public AutoSuggestBoxAccessor(AutoSuggestBox autoSuggestBox) + { + AutoSuggestBox = autoSuggestBox; + } + + public AutoSuggestBox AutoSuggestBox { get; private set; } } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml index cef3bfa2..5358afed 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml @@ -32,6 +32,13 @@ Source="{Binding Converter={StaticResource PropertyDescriptor}}"/> + + + + @@ -132,20 +139,16 @@ - - - - - - + Text="{Binding FilterText, Mode=TwoWay}"/> public WikiWeaponPage() { - InitializeWith(); + WikiWeaponViewModel viewModel = InitializeWith(); InitializeComponent(); + + viewModel.Initialize(new AutoSuggestBoxAccessor(WeaponSuggestBox)); + } + + private class AutoSuggestBoxAccessor : IAutoSuggestBoxAccessor + { + public AutoSuggestBoxAccessor(AutoSuggestBox autoSuggestBox) + { + AutoSuggestBox = autoSuggestBox; + } + + public AutoSuggestBox AutoSuggestBox { get; private set; } } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IAutoSuggestBoxAccessor.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IAutoSuggestBoxAccessor.cs new file mode 100644 index 00000000..2f5233ca --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IAutoSuggestBoxAccessor.cs @@ -0,0 +1,12 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Xaml.Controls; +using Snap.Hutao.Control; + +namespace Snap.Hutao.ViewModel.Wiki; + +internal interface IAutoSuggestBoxAccessor : IXamlElementAccessor +{ + AutoSuggestBox AutoSuggestBox { get; } +} diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IWikiViewModelInitialization.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IWikiViewModelInitialization.cs new file mode 100644 index 00000000..45335020 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IWikiViewModelInitialization.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.ViewModel.Wiki; + +internal interface IWikiViewModelInitialization +{ + void Initialize(IAutoSuggestBoxAccessor accessor); +} diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index 250197cb..2c2283cc 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -1,11 +1,13 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Control.Collection.AdvancedCollectionView; using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Model.Calculable; using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Intrinsic.Frozen; using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Item; @@ -17,6 +19,7 @@ using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.View.Dialog; using Snap.Hutao.Web.Response; +using System.Collections.Frozen; using System.Runtime.InteropServices; using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; @@ -32,7 +35,7 @@ namespace Snap.Hutao.ViewModel.Wiki; [HighQuality] [ConstructorGenerated] [Injection(InjectAs.Scoped)] -internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel +internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel, IWikiViewModelInitialization { private readonly IContentDialogFactory contentDialogFactory; private readonly ICultivationService cultivationService; @@ -49,6 +52,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel private BaseValueInfo? baseValueInfo; private Dictionary>? levelAvatarCurveMap; private List? promotes; + private FrozenSet availableQueries; /// /// 角色列表 @@ -79,6 +83,13 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel /// public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); } + public void Initialize(IAutoSuggestBoxAccessor accessor) + { + accessor.AutoSuggestBox.TextChanged += OnFilterSuggestionRequested; + accessor.AutoSuggestBox.SuggestionChosen += OnFilterSuggestionChosen; + accessor.AutoSuggestBox.QuerySubmitted += ApplyFilter; + } + protected override async ValueTask InitializeUIAsync() { if (!await metadataService.InitializeAsync().ConfigureAwait(false)) @@ -101,6 +112,17 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel await taskContext.SwitchToMainThreadAsync(); Avatars = new(list, true); Selected = Avatars.View.ElementAtOrDefault(0); + + availableQueries = FrozenSet.ToFrozenSet( + [ + .. avatars.Select(a => a.Name), + .. IntrinsicFrozen.AssociationTypes, + .. IntrinsicFrozen.BodyTypes, + .. IntrinsicFrozen.ElementNames, + .. IntrinsicFrozen.ItemQualities, + .. IntrinsicFrozen.WeaponTypes, + ]); + return true; } @@ -202,21 +224,43 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel BaseValueInfo = new(avatar.MaxLevel, propertyCurveValues, levelAvatarCurveMap, avatarPromoteMap); } - [Command("FilterCommand")] - private void ApplyFilter(string? input) + private void OnFilterSuggestionRequested(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { if (Avatars is null) { return; } - if (string.IsNullOrWhiteSpace(input)) + if (string.IsNullOrWhiteSpace(FilterText)) + { + return; + } + + if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + { + sender.ItemsSource = availableQueries.Where(q => q.Contains(FilterText, StringComparison.OrdinalIgnoreCase)); + } + } + + private void OnFilterSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + { + sender.Text = args.SelectedItem.ToString(); + } + + private void ApplyFilter(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + { + if (Avatars is null) + { + return; + } + + if (string.IsNullOrWhiteSpace(FilterText)) { Avatars.Filter = default!; return; } - Avatars.Filter = AvatarFilter.Compile(input); + Avatars.Filter = AvatarFilter.Compile(FilterText); if (Selected is not null && Avatars.Contains(Selected)) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs index 37615619..2cc99cbb 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -1,11 +1,13 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Control.Collection.AdvancedCollectionView; using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Model.Calculable; using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Intrinsic.Frozen; using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata.Item; using Snap.Hutao.Model.Metadata.Weapon; @@ -17,6 +19,7 @@ using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.View.Dialog; using Snap.Hutao.Web.Response; +using System.Collections.Frozen; using System.Runtime.InteropServices; using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; @@ -29,7 +32,7 @@ namespace Snap.Hutao.ViewModel.Wiki; /// [ConstructorGenerated] [Injection(InjectAs.Scoped)] -internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel +internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel, IWikiViewModelInitialization { private readonly IContentDialogFactory contentDialogFactory; private readonly CalculateClient calculateClient; @@ -46,6 +49,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel private BaseValueInfo? baseValueInfo; private Dictionary>? levelWeaponCurveMap; private List? promotes; + private FrozenSet availableQueries; /// /// 角色列表 @@ -76,6 +80,13 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel /// public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); } + public void Initialize(IAutoSuggestBoxAccessor accessor) + { + accessor.AutoSuggestBox.TextChanged += OnFilterSuggestionRequested; + accessor.AutoSuggestBox.SuggestionChosen += OnFilterSuggestionChosen; + accessor.AutoSuggestBox.QuerySubmitted += ApplyFilter; + } + /// protected override async Task OpenUIAsync() { @@ -98,6 +109,14 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel Weapons = new(list, true); Selected = Weapons.View.ElementAtOrDefault(0); + + availableQueries = FrozenSet.ToFrozenSet( + [ + .. weapons.Select(w => w.Name), + .. IntrinsicFrozen.ItemQualities, + .. IntrinsicFrozen.FightProperties, + .. IntrinsicFrozen.WeaponTypes, + ]); } } @@ -187,21 +206,43 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel BaseValueInfo = new(weapon.MaxLevel, propertyCurveValues, levelWeaponCurveMap, weaponPromoteMap); } - [Command("FilterCommand")] - private void ApplyFilter(string? input) + private void OnFilterSuggestionRequested(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { if (Weapons is null) { return; } - if (string.IsNullOrWhiteSpace(input)) + if (string.IsNullOrWhiteSpace(FilterText)) + { + return; + } + + if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + { + sender.ItemsSource = availableQueries.Where(q => q.Contains(FilterText, StringComparison.OrdinalIgnoreCase)); + } + } + + private void OnFilterSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + { + sender.Text = args.SelectedItem.ToString(); + } + + private void ApplyFilter(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + { + if (Weapons is null) + { + return; + } + + if (string.IsNullOrWhiteSpace(FilterText)) { Weapons.Filter = default!; return; } - Weapons.Filter = WeaponFilter.Compile(input); + Weapons.Filter = WeaponFilter.Compile(FilterText); if (Selected is not null && Weapons.Contains(Selected)) {