diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml index 92ab950a..eca83605 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml @@ -6,6 +6,7 @@ + diff --git a/src/Snap.Hutao/Snap.Hutao/Control/AutoSuggestBox/AutoSuggestTokenBox.cs b/src/Snap.Hutao/Snap.Hutao/Control/AutoSuggestBox/AutoSuggestTokenBox.cs new file mode 100644 index 00000000..7c3b6782 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/AutoSuggestBox/AutoSuggestTokenBox.cs @@ -0,0 +1,67 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using CommunityToolkit.WinUI; +using CommunityToolkit.WinUI.Controls; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Snap.Hutao.Control.Extension; + +namespace Snap.Hutao.Control.AutoSuggestBox; + +[DependencyProperty("FilterCommand", typeof(ICommand))] +[DependencyProperty("FilterCommandParameter", typeof(object))] +[DependencyProperty("AvailableTokens", typeof(IReadOnlyDictionary))] +internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox +{ + public AutoSuggestTokenBox() + { + DefaultStyleKey = typeof(TokenizingTextBox); + TextChanged += OnFilterSuggestionRequested; + QuerySubmitted += OnQuerySubmitted; + TokenItemAdding += OnTokenItemAdding; + TokenItemAdded += OnTokenItemModified; + TokenItemRemoved += OnTokenItemModified; + } + + private void OnFilterSuggestionRequested(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if (string.IsNullOrWhiteSpace(Text)) + { + return; + } + + if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + { + sender.ItemsSource = AvailableTokens.Values.Where(q => q.Value.Contains(Text, StringComparison.OrdinalIgnoreCase)); + + // TODO: CornerRadius + // Popup? popup = this.FindDescendant("SuggestionsPopup") as Popup; + } + } + + private void OnQuerySubmitted(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + { + if (args.ChosenSuggestion is not null) + { + return; + } + + CommandExtension.TryExecute(FilterCommand, FilterCommandParameter); + } + + private void OnTokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args) + { + if (string.IsNullOrWhiteSpace(args.TokenText)) + { + return; + } + + args.Item = AvailableTokens.GetValueOrDefault(args.TokenText) ?? new SearchToken(SearchTokenKind.None, args.TokenText); + } + + private void OnTokenItemModified(TokenizingTextBox sender, object args) + { + CommandExtension.TryExecute(FilterCommand, FilterCommandParameter); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/AutoSuggestBox/SearchToken.cs b/src/Snap.Hutao/Snap.Hutao/Control/AutoSuggestBox/SearchToken.cs new file mode 100644 index 00000000..cfa8dc91 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/AutoSuggestBox/SearchToken.cs @@ -0,0 +1,33 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Windows.UI; + +namespace Snap.Hutao.Control.AutoSuggestBox; + +internal sealed class SearchToken +{ + public SearchToken(SearchTokenKind kind, string value, Uri? iconUri = null, Uri? sideIconUri = null, Color? quality = null) + { + Value = value; + Kind = kind; + IconUri = iconUri; + SideIconUri = sideIconUri; + Quality = quality; + } + + public SearchTokenKind Kind { get; } + + public string Value { get; set; } = default!; + + public Uri? IconUri { get; } + + public Uri? SideIconUri { get; } + + public Color? Quality { get; } + + public override string ToString() + { + return Value; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/AutoSuggestBox/SearchTokenKind.cs b/src/Snap.Hutao/Snap.Hutao/Control/AutoSuggestBox/SearchTokenKind.cs new file mode 100644 index 00000000..4a155c86 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/AutoSuggestBox/SearchTokenKind.cs @@ -0,0 +1,17 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Control.AutoSuggestBox; + +internal enum SearchTokenKind +{ + None, + AssociationType, + Avatar, + BodyType, + ElementName, + FightProperty, + ItemQuality, + Weapon, + WeaponType, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnLoadedBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnLoadedBehavior.cs index 26697a31..954d61cd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnLoadedBehavior.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnLoadedBehavior.cs @@ -3,6 +3,7 @@ using CommunityToolkit.WinUI.Behaviors; using Microsoft.UI.Xaml; +using Snap.Hutao.Control.Extension; namespace Snap.Hutao.Control.Behavior; @@ -45,10 +46,6 @@ internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase IndexTypeMap = new() - { - [0] = List, - [1] = Grid, - }; + private static readonly FrozenDictionary IndexTypeMap = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(0, List), + KeyValuePair.Create(1, Grid), + ]); private readonly RoutedEventHandler loadedEventHandler; private readonly RoutedEventHandler unloadedEventHandler; diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/KnownColors.cs b/src/Snap.Hutao/Snap.Hutao/Control/Theme/KnownColors.cs index 980be1a5..c69a841a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/KnownColors.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/KnownColors.cs @@ -11,4 +11,6 @@ internal static class KnownColors public static readonly Color Orange = StructMarshal.Color(0xFFBC6932); public static readonly Color Purple = StructMarshal.Color(0xFFA156E0); public static readonly Color Blue = StructMarshal.Color(0xFF5180CB); + public static readonly Color Green = StructMarshal.Color(0xFF2A8F72); + public static readonly Color White = StructMarshal.Color(0xFF72778B); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs index 84a95a90..5fcf38ef 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs @@ -26,12 +26,12 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera { private const string CacheFolderName = nameof(ImageCache); - private readonly FrozenDictionary retryCountToDelay = new Dictionary() - { - [0] = TimeSpan.FromSeconds(4), - [1] = TimeSpan.FromSeconds(16), - [2] = TimeSpan.FromSeconds(64), - }.ToFrozenDictionary(); + private readonly FrozenDictionary retryCountToDelay = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(0, TimeSpan.FromSeconds(4)), + KeyValuePair.Create(1, TimeSpan.FromSeconds(16)), + KeyValuePair.Create(2, TimeSpan.FromSeconds(64)), + ]); private readonly ConcurrentDictionary concurrentTasks = new(); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs index 66b635b9..b01f5ebe 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Frozen; using System.Text; namespace Snap.Hutao.Core; @@ -14,25 +15,25 @@ internal static class TypeNameHelper { private const char DefaultNestedTypeDelimiter = '+'; - private static readonly Dictionary BuiltInTypeNames = new() - { - { typeof(void), "void" }, - { typeof(bool), "bool" }, - { typeof(byte), "byte" }, - { typeof(char), "char" }, - { typeof(decimal), "decimal" }, - { typeof(double), "double" }, - { typeof(float), "float" }, - { typeof(int), "int" }, - { typeof(long), "long" }, - { typeof(object), "object" }, - { typeof(sbyte), "sbyte" }, - { typeof(short), "short" }, - { typeof(string), "string" }, - { typeof(uint), "uint" }, - { typeof(ulong), "ulong" }, - { typeof(ushort), "ushort" }, - }; + private static readonly FrozenDictionary BuiltInTypeNames = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(typeof(void), "void"), + KeyValuePair.Create(typeof(bool), "bool"), + KeyValuePair.Create(typeof(byte), "byte"), + KeyValuePair.Create(typeof(char), "char"), + KeyValuePair.Create(typeof(decimal), "decimal"), + KeyValuePair.Create(typeof(double), "double"), + KeyValuePair.Create(typeof(float), "float"), + KeyValuePair.Create(typeof(int), "int"), + KeyValuePair.Create(typeof(long), "long"), + KeyValuePair.Create(typeof(object), "object"), + KeyValuePair.Create(typeof(sbyte), "sbyte"), + KeyValuePair.Create(typeof(short), "short"), + KeyValuePair.Create(typeof(string), "string"), + KeyValuePair.Create(typeof(uint), "uint"), + KeyValuePair.Create(typeof(ulong), "ulong"), + KeyValuePair.Create(typeof(ushort), "ushort"), + ]); /// /// 获取对象类型的显示名称 diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs index ac878e41..85fb49f9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.cs @@ -15,6 +15,12 @@ namespace Snap.Hutao.Extension; [HighQuality] internal static partial class EnumerableExtension { + public static void Deconstruct(this IGrouping grouping, out TKey key, out IEnumerable elements) + { + key = grouping.Key; + elements = grouping; + } + public static TElement? ElementAtOrLastOrDefault(this IEnumerable source, int index) { return source.ElementAtOrDefault(index) ?? source.LastOrDefault(); diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AssociationTypeIconConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AssociationTypeIconConverter.cs new file mode 100644 index 00000000..e307895f --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AssociationTypeIconConverter.cs @@ -0,0 +1,55 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Control; +using Snap.Hutao.Model.Intrinsic; +using System.Collections.Frozen; + +namespace Snap.Hutao.Model.Metadata.Converter; + +internal sealed class AssociationTypeIconConverter : ValueConverter +{ + private static readonly FrozenDictionary LocalizedNameToAssociationType = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeMondstadt, AssociationType.ASSOC_TYPE_MONDSTADT), + KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeLiyue, AssociationType.ASSOC_TYPE_LIYUE), + KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFatui, AssociationType.ASSOC_TYPE_FATUI), + KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeInazuma, AssociationType.ASSOC_TYPE_INAZUMA), + KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeRanger, AssociationType.ASSOC_TYPE_RANGER), + KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSumeru, AssociationType.ASSOC_TYPE_SUMERU), + KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFontaine, AssociationType.ASSOC_TYPE_FONTAINE), + KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeNatlan, AssociationType.ASSOC_TYPE_NATLAN), + KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSnezhnaya, AssociationType.ASSOC_TYPE_SNEZHNAYA), + ]); + + public static Uri? AssociationTypeNameToIconUri(string associationTypeName) + { + return AssociationTypeToIconUri(LocalizedNameToAssociationType.GetValueOrDefault(associationTypeName)); + } + + public static Uri? AssociationTypeToIconUri(AssociationType type) + { + string? association = type switch + { + AssociationType.ASSOC_TYPE_MONDSTADT => "Mengde", + AssociationType.ASSOC_TYPE_LIYUE => "Liyue", + AssociationType.ASSOC_TYPE_FATUI => default, + AssociationType.ASSOC_TYPE_INAZUMA => "Inazuma", + AssociationType.ASSOC_TYPE_RANGER => default, + AssociationType.ASSOC_TYPE_SUMERU => "Sumeru", + AssociationType.ASSOC_TYPE_FONTAINE => "Fontaine", + AssociationType.ASSOC_TYPE_NATLAN => default, + AssociationType.ASSOC_TYPE_SNEZHNAYA => default, + _ => throw Must.NeverHappen(), + }; + + return association is null + ? default + : Web.HutaoEndpoints.StaticRaw("ChapterIcon", $"UI_ChapterIcon_{association}.png").ToUri(); + } + + public override Uri? Convert(AssociationType from) + { + return AssociationTypeToIconUri(from); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ElementNameIconConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ElementNameIconConverter.cs index 34e2230b..a2579baa 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ElementNameIconConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/ElementNameIconConverter.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Control; using Snap.Hutao.Model.Intrinsic; +using System.Collections.Frozen; namespace Snap.Hutao.Model.Metadata.Converter; @@ -12,27 +13,27 @@ namespace Snap.Hutao.Model.Metadata.Converter; [HighQuality] internal sealed class ElementNameIconConverter : ValueConverter { - private static readonly Dictionary LocalizedNameToElementIconName = new() - { - [SH.ModelIntrinsicElementNameElec] = "Electric", - [SH.ModelIntrinsicElementNameFire] = "Fire", - [SH.ModelIntrinsicElementNameGrass] = "Grass", - [SH.ModelIntrinsicElementNameIce] = "Ice", - [SH.ModelIntrinsicElementNameRock] = "Rock", - [SH.ModelIntrinsicElementNameWater] = "Water", - [SH.ModelIntrinsicElementNameWind] = "Wind", - }; + private static readonly FrozenDictionary LocalizedNameToElementIconName = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(SH.ModelIntrinsicElementNameElec, "Electric"), + KeyValuePair.Create(SH.ModelIntrinsicElementNameFire, "Fire"), + KeyValuePair.Create(SH.ModelIntrinsicElementNameGrass, "Grass"), + KeyValuePair.Create(SH.ModelIntrinsicElementNameIce, "Ice"), + KeyValuePair.Create(SH.ModelIntrinsicElementNameRock, "Rock"), + KeyValuePair.Create(SH.ModelIntrinsicElementNameWater, "Water"), + KeyValuePair.Create(SH.ModelIntrinsicElementNameWind, "Wind"), + ]); - private static readonly Dictionary LocalizedNameToElementType = new() - { - [SH.ModelIntrinsicElementNameElec] = ElementType.Electric, - [SH.ModelIntrinsicElementNameFire] = ElementType.Fire, - [SH.ModelIntrinsicElementNameGrass] = ElementType.Grass, - [SH.ModelIntrinsicElementNameIce] = ElementType.Ice, - [SH.ModelIntrinsicElementNameRock] = ElementType.Rock, - [SH.ModelIntrinsicElementNameWater] = ElementType.Water, - [SH.ModelIntrinsicElementNameWind] = ElementType.Wind, - }; + private static readonly FrozenDictionary LocalizedNameToElementType = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(SH.ModelIntrinsicElementNameElec, ElementType.Electric), + KeyValuePair.Create(SH.ModelIntrinsicElementNameFire, ElementType.Fire), + KeyValuePair.Create(SH.ModelIntrinsicElementNameGrass, ElementType.Grass), + KeyValuePair.Create(SH.ModelIntrinsicElementNameIce, ElementType.Ice), + KeyValuePair.Create(SH.ModelIntrinsicElementNameRock, ElementType.Rock), + KeyValuePair.Create(SH.ModelIntrinsicElementNameWater, ElementType.Water), + KeyValuePair.Create(SH.ModelIntrinsicElementNameWind, ElementType.Wind), + ]); /// /// 将中文元素名称转换为图标链接 diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/QualityColorConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/QualityColorConverter.cs index 20df90d1..a2dda53c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/QualityColorConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/QualityColorConverter.cs @@ -3,8 +3,9 @@ using Microsoft.UI; using Snap.Hutao.Control; +using Snap.Hutao.Control.Theme; using Snap.Hutao.Model.Intrinsic; -using Snap.Hutao.Win32; +using System.Collections.Frozen; using Windows.UI; namespace Snap.Hutao.Model.Metadata.Converter; @@ -15,17 +16,39 @@ namespace Snap.Hutao.Model.Metadata.Converter; [HighQuality] internal sealed class QualityColorConverter : ValueConverter { + private static readonly FrozenDictionary LocalizedNameToQualityType = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(SH.ModelIntrinsicItemQualityWhite, QualityType.QUALITY_WHITE), + KeyValuePair.Create(SH.ModelIntrinsicItemQualityGreen, QualityType.QUALITY_GREEN), + KeyValuePair.Create(SH.ModelIntrinsicItemQualityBlue, QualityType.QUALITY_BLUE), + KeyValuePair.Create(SH.ModelIntrinsicItemQualityPurple, QualityType.QUALITY_PURPLE), + KeyValuePair.Create(SH.ModelIntrinsicItemQualityOrange, QualityType.QUALITY_ORANGE), + KeyValuePair.Create(SH.ModelIntrinsicItemQualityRed, QualityType.QUALITY_ORANGE_SP), + ]); + + private static readonly FrozenDictionary QualityTypeToColor = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(QualityType.QUALITY_WHITE, KnownColors.White), + KeyValuePair.Create(QualityType.QUALITY_GREEN, KnownColors.Green), + KeyValuePair.Create(QualityType.QUALITY_BLUE, KnownColors.Blue), + KeyValuePair.Create(QualityType.QUALITY_PURPLE, KnownColors.Purple), + KeyValuePair.Create(QualityType.QUALITY_ORANGE, KnownColors.Orange), + KeyValuePair.Create(QualityType.QUALITY_ORANGE_SP, KnownColors.Orange), + ]); + + public static Color QualityNameToColor(string qualityName) + { + return QualityToColor(LocalizedNameToQualityType.GetValueOrDefault(qualityName)); + } + + public static Color QualityToColor(QualityType quality) + { + return QualityTypeToColor.GetValueOrDefault(quality, Colors.Transparent); + } + /// public override Color Convert(QualityType from) { - return from switch - { - QualityType.QUALITY_WHITE => StructMarshal.Color(0xFF72778B), - QualityType.QUALITY_GREEN => StructMarshal.Color(0xFF2A8F72), - QualityType.QUALITY_BLUE => StructMarshal.Color(0xFF5180CB), - QualityType.QUALITY_PURPLE => StructMarshal.Color(0xFFA156E0), - QualityType.QUALITY_ORANGE or QualityType.QUALITY_ORANGE_SP => StructMarshal.Color(0xFFBC6932), - _ => Colors.Transparent, - }; + return QualityToColor(from); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/WeaponTypeIconConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/WeaponTypeIconConverter.cs index e6f34fe4..9768a170 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/WeaponTypeIconConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/WeaponTypeIconConverter.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Control; using Snap.Hutao.Model.Intrinsic; +using System.Collections.Frozen; namespace Snap.Hutao.Model.Metadata.Converter; @@ -12,6 +13,20 @@ namespace Snap.Hutao.Model.Metadata.Converter; [HighQuality] internal sealed class WeaponTypeIconConverter : ValueConverter { + private static readonly FrozenDictionary LocalizedNameToWeaponType = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeSwordOneHand, WeaponType.WEAPON_SWORD_ONE_HAND), + KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeBow, WeaponType.WEAPON_BOW), + KeyValuePair.Create(SH.ModelIntrinsicWeaponTypePole, WeaponType.WEAPON_POLE), + KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeClaymore, WeaponType.WEAPON_CLAYMORE), + KeyValuePair.Create(SH.ModelIntrinsicWeaponTypeCatalyst, WeaponType.WEAPON_CATALYST), + ]); + + public static Uri WeaponTypeNameToIconUri(string weaponTypeName) + { + return WeaponTypeToIconUri(LocalizedNameToWeaponType.GetValueOrDefault(weaponTypeName)); + } + /// /// 将武器类型转换为图标链接 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaConfigTypeComparer.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaConfigTypeComparer.cs index c396f2ba..533ff220 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaConfigTypeComparer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaConfigTypeComparer.cs @@ -13,14 +13,14 @@ namespace Snap.Hutao.Service.GachaLog.Factory; internal sealed class GachaConfigTypeComparer : IComparer { private static readonly Lazy LazyShared = new(() => new()); - private static readonly FrozenDictionary OrderMap = new Dictionary() - { - [GachaConfigType.AvatarEventWish] = 0, - [GachaConfigType.AvatarEventWish2] = 1, - [GachaConfigType.WeaponEventWish] = 2, - [GachaConfigType.StandardWish] = 3, - [GachaConfigType.NoviceWish] = 4, - }.ToFrozenDictionary(); + private static readonly FrozenDictionary OrderMap = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(GachaConfigType.AvatarEventWish, 0), + KeyValuePair.Create(GachaConfigType.AvatarEventWish2, 1), + KeyValuePair.Create(GachaConfigType.WeaponEventWish, 2), + KeyValuePair.Create(GachaConfigType.StandardWish, 3), + KeyValuePair.Create(GachaConfigType.NoviceWish, 4), + ]); /// /// 共享的比较器 diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index cb0678ac..bd6cc9cd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -298,6 +298,7 @@ + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs index 6d98101b..4fbc1f56 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/AnnouncementContentViewer.xaml.cs @@ -7,6 +7,7 @@ using Microsoft.Web.WebView2.Core; using Snap.Hutao.Control.Extension; using Snap.Hutao.Control.Theme; using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; +using System.Collections.Frozen; using System.Text; using System.Text.RegularExpressions; using Windows.Foundation; @@ -29,17 +30,17 @@ internal sealed partial class AnnouncementContentViewer : UserControl } """; - private static readonly Dictionary DarkLightReverts = new() - { - { "color:rgba(0,0,0,1)", "color:rgba(255,255,255,1)" }, - { "color:rgba(17,17,17,1)", "color:rgba(238,238,238,1)" }, - { "color:rgba(51,51,51,1)", "color:rgba(204,204,204,1)" }, - { "color:rgba(57,59,64,1)", "color:rgba(198,196,191,1)" }, - { "color:rgba(85,85,85,1)", "color:rgba(170,170,170,1)" }, - { "background-color: rgb(255, 215, 185)", "background-color: rgb(0,40,70)" }, - { "background-color: rgb(254, 245, 231)", "background-color: rgb(1,40,70)" }, - { "background-color:rgb(244, 244, 245)", "background-color:rgba(11, 11, 10)" }, - }; + private static readonly FrozenDictionary DarkLightReverts = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create("color:rgba(0,0,0,1)", "color:rgba(255,255,255,1)"), + KeyValuePair.Create("color:rgba(17,17,17,1)", "color:rgba(238,238,238,1)"), + KeyValuePair.Create("color:rgba(51,51,51,1)", "color:rgba(204,204,204,1)"), + KeyValuePair.Create("color:rgba(57,59,64,1)", "color:rgba(198,196,191,1)"), + KeyValuePair.Create("color:rgba(85,85,85,1)", "color:rgba(170,170,170,1)"), + KeyValuePair.Create("background-color: rgb(255, 215, 185)", "background-color: rgb(0,40,70)"), + KeyValuePair.Create("background-color: rgb(254, 245, 231)", "background-color: rgb(1,40,70)"), + KeyValuePair.Create("background-color:rgb(244, 244, 245)", "background-color:rgba(11, 11, 10)"), + ]); private readonly RoutedEventHandler loadEventHandler; private readonly RoutedEventHandler unloadEventHandler; diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml index 1bcfbd91..2b5445d9 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml @@ -7,8 +7,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mxi="using:Microsoft.Xaml.Interactivity" - xmlns:mxic="using:Microsoft.Xaml.Interactions.Core" xmlns:shc="using:Snap.Hutao.Control" + xmlns:shca="using:Snap.Hutao.Control.AutoSuggestBox" xmlns:shcb="using:Snap.Hutao.Control.Behavior" xmlns:shci="using:Snap.Hutao.Control.Image" xmlns:shcm="using:Snap.Hutao.Control.Markup" @@ -73,6 +73,47 @@ + + + + + + + + + + + + + + + + + @@ -263,24 +304,33 @@ Margin="8,8,0,0" LocalSettingKeySuffixForCurrent="WikiAvatarPage.Avatars"/> - - - - - - - - + QueryIcon="{cw:FontIconSource Glyph=}" + Style="{StaticResource DefaultTokenizingTextBoxStyle}" + SuggestedItemTemplate="{StaticResource TokenTemplate}" + SuggestedItemsSource="{Binding AvailableTokens.Values}" + Text="{Binding FilterToken, Mode=TwoWay}" + TokenItemTemplate="{StaticResource TokenTemplate}"> + + + + + + + + + + + + + + + + + + + + + + + @@ -131,21 +173,32 @@ LocalSettingKeySuffixForCurrent="WikiWeaponPage.Weapons"/> - - - - - - - + QueryIcon="{cw:FontIconSource Glyph=}" + SuggestedItemTemplate="{StaticResource TokenTemplate}" + SuggestedItemsSource="{Binding AvailableTokens.Values}" + Text="{Binding FilterToken, Mode=TwoWay}" + TokenItemTemplate="{StaticResource TokenTemplate}"> + + + + + + PropertyIcons = new Dictionary() - { - [FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_CDReduce.png").ToUri(), - [FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_ChargeEfficiency.png").ToUri(), - [FightProperty.FIGHT_PROP_CRITICAL] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Critical.png").ToUri(), - [FightProperty.FIGHT_PROP_CRITICAL_HURT] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Critical_Hurt.png").ToUri(), - [FightProperty.FIGHT_PROP_CUR_ATTACK] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_CurAttack.png").ToUri(), - [FightProperty.FIGHT_PROP_CUR_DEFENSE] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_CurDefense.png").ToUri(), - [FightProperty.FIGHT_PROP_ELEMENT_MASTERY] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element.png").ToUri(), - [FightProperty.FIGHT_PROP_ELEC_ADD_HURT] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Electric.png").ToUri(), - [FightProperty.FIGHT_PROP_FIRE_ADD_HURT] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Fire.png").ToUri(), - [FightProperty.FIGHT_PROP_GRASS_ADD_HURT] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Grass.png").ToUri(), - [FightProperty.FIGHT_PROP_ICE_ADD_HURT] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Ice.png").ToUri(), - [FightProperty.FIGHT_PROP_ROCK_ADD_HURT] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Rock.png").ToUri(), - [FightProperty.FIGHT_PROP_WATER_ADD_HURT] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Water.png").ToUri(), - [FightProperty.FIGHT_PROP_WIND_ADD_HURT] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Wind.png").ToUri(), - [FightProperty.FIGHT_PROP_HEAL_ADD] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Heal.png").ToUri(), - [FightProperty.FIGHT_PROP_MAX_HP] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_MaxHp.png").ToUri(), - [FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_PhysicalAttackUp.png").ToUri(), - [FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO] = Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_ShieldCostMinus.png").ToUri(), - }.ToFrozenDictionary(); + private static readonly FrozenDictionary PropertyIcons = FrozenDictionary.ToFrozenDictionary( + [ + KeyValuePair.Create(FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_CDReduce.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_ChargeEfficiency.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_CRITICAL, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Critical.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_CRITICAL_HURT, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Critical_Hurt.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_CUR_ATTACK, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_CurAttack.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_CUR_DEFENSE, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_CurDefense.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_ELEMENT_MASTERY, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_ELEC_ADD_HURT, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Electric.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_FIRE_ADD_HURT, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Fire.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_GRASS_ADD_HURT, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Grass.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_ICE_ADD_HURT, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Ice.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_ROCK_ADD_HURT, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Rock.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_WATER_ADD_HURT, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Water.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_WIND_ADD_HURT, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Element_Wind.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_HEAL_ADD, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_Heal.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_MAX_HP, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_MaxHp.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_PhysicalAttackUp.png").ToUri()), + KeyValuePair.Create(FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, Web.HutaoEndpoints.StaticRaw("Property", "UI_Icon_ShieldCostMinus.png").ToUri()), + ]); private Brush? background; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs index 2d765c25..911bded0 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs @@ -1,9 +1,10 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Microsoft.Extensions.Primitives; +using Snap.Hutao.Control.AutoSuggestBox; using Snap.Hutao.Model.Intrinsic.Frozen; using Snap.Hutao.Model.Metadata.Avatar; +using System.Collections.ObjectModel; namespace Snap.Hutao.ViewModel.Wiki; @@ -17,55 +18,61 @@ internal static class AvatarFilter /// /// 输入 /// 筛选操作 - public static Predicate Compile(string input) + public static Predicate Compile(ObservableCollection input) { return (Avatar avatar) => DoFilter(input, avatar); } - private static bool DoFilter(string input, Avatar avatar) + private static bool DoFilter(ObservableCollection input, Avatar avatar) { List matches = []; - foreach (StringSegment segment in new StringTokenizer(input, [' '])) + + foreach ((SearchTokenKind kind, IEnumerable tokens) in input.GroupBy(token => token.Kind, token => token.Value)) { - string value = segment.ToString(); - - if (avatar.Name == value) + switch (kind) { - matches.Add(true); - continue; - } + case SearchTokenKind.ElementName: + if (IntrinsicFrozen.ElementNames.Overlaps(tokens)) + { + matches.Add(tokens.Contains(avatar.FetterInfo.VisionBefore)); + } - if (IntrinsicFrozen.ElementNames.Contains(value)) - { - matches.Add(avatar.FetterInfo.VisionBefore == value); - continue; - } + break; + case SearchTokenKind.AssociationType: + if (IntrinsicFrozen.AssociationTypes.Overlaps(tokens)) + { + matches.Add(tokens.Contains(avatar.FetterInfo.Association.GetLocalizedDescriptionOrDefault())); + } - if (IntrinsicFrozen.AssociationTypes.Contains(value)) - { - matches.Add(avatar.FetterInfo.Association.GetLocalizedDescriptionOrDefault() == value); - continue; - } + break; + case SearchTokenKind.WeaponType: + if (IntrinsicFrozen.WeaponTypes.Overlaps(tokens)) + { + matches.Add(tokens.Contains(avatar.Weapon.GetLocalizedDescriptionOrDefault())); + } - if (IntrinsicFrozen.WeaponTypes.Contains(value)) - { - matches.Add(avatar.Weapon.GetLocalizedDescriptionOrDefault() == value); - continue; - } + break; + case SearchTokenKind.ItemQuality: + if (IntrinsicFrozen.ItemQualities.Overlaps(tokens)) + { + matches.Add(tokens.Contains(avatar.Quality.GetLocalizedDescriptionOrDefault())); + } - if (IntrinsicFrozen.ItemQualities.Contains(value)) - { - matches.Add(avatar.Quality.GetLocalizedDescriptionOrDefault() == value); - continue; - } + break; + case SearchTokenKind.BodyType: + if (IntrinsicFrozen.BodyTypes.Overlaps(tokens)) + { + matches.Add(tokens.Contains(avatar.Body.GetLocalizedDescriptionOrDefault())); + } - if (IntrinsicFrozen.BodyTypes.Contains(value)) - { - matches.Add(avatar.Body.GetLocalizedDescriptionOrDefault() == value); - continue; + break; + case SearchTokenKind.Avatar: + matches.Add(tokens.Contains(avatar.Name)); + break; + default: + matches.Add(false); + break; } - - matches.Add(false); } return matches.Count > 0 && matches.Aggregate((a, b) => a && b); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs index 06731581..359f258d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs @@ -1,9 +1,10 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Microsoft.Extensions.Primitives; +using Snap.Hutao.Control.AutoSuggestBox; using Snap.Hutao.Model.Intrinsic.Frozen; using Snap.Hutao.Model.Metadata.Weapon; +using System.Collections.ObjectModel; namespace Snap.Hutao.ViewModel.Wiki; @@ -17,41 +18,46 @@ internal static class WeaponFilter /// /// 输入 /// 筛选操作 - public static Predicate Compile(string input) + public static Predicate Compile(ObservableCollection input) { return (Weapon weapon) => DoFilter(input, weapon); } - private static bool DoFilter(string input, Weapon weapon) + private static bool DoFilter(ObservableCollection input, Weapon weapon) { List matches = []; - foreach (StringSegment segment in new StringTokenizer(input, [' '])) + foreach ((SearchTokenKind kind, IEnumerable tokens) in input.GroupBy(token => token.Kind, token => token.Value)) { - string value = segment.ToString(); - - if (weapon.Name == value) + switch (kind) { - matches.Add(true); - continue; - } + case SearchTokenKind.WeaponType: + if (IntrinsicFrozen.WeaponTypes.Overlaps(tokens)) + { + matches.Add(tokens.Contains(weapon.WeaponType.GetLocalizedDescriptionOrDefault())); + } - if (IntrinsicFrozen.WeaponTypes.Contains(value)) - { - matches.Add(weapon.WeaponType.GetLocalizedDescriptionOrDefault() == value); - continue; - } + break; + case SearchTokenKind.ItemQuality: + if (IntrinsicFrozen.ItemQualities.Overlaps(tokens)) + { + matches.Add(tokens.Contains(weapon.Quality.GetLocalizedDescriptionOrDefault())); + } - if (IntrinsicFrozen.ItemQualities.Contains(value)) - { - matches.Add(weapon.Quality.GetLocalizedDescriptionOrDefault() == value); - continue; - } + break; + case SearchTokenKind.FightProperty: + if (IntrinsicFrozen.FightProperties.Overlaps(tokens)) + { + matches.Add(tokens.Contains(weapon.GrowCurves.ElementAtOrDefault(1)?.Type.GetLocalizedDescriptionOrDefault())); + } - if (IntrinsicFrozen.FightProperties.Contains(value)) - { - matches.Add(weapon.GrowCurves.ElementAtOrDefault(1)?.Type.GetLocalizedDescriptionOrDefault() == value); - continue; + break; + case SearchTokenKind.Weapon: + matches.Add(tokens.Contains(weapon.Name)); + break; + default: + matches.Add(false); + break; } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index 250197cb..5b526fca 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -1,13 +1,16 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Control.AutoSuggestBox; 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.Converter; using Snap.Hutao.Model.Metadata.Item; using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.Cultivation; @@ -17,6 +20,8 @@ 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.Collections.ObjectModel; using System.Runtime.InteropServices; using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; @@ -45,10 +50,12 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel private AdvancedCollectionView? avatars; private Avatar? selected; - private string? filterText; + private ObservableCollection? filterTokens; + private string? filterToken; private BaseValueInfo? baseValueInfo; private Dictionary>? levelAvatarCurveMap; private List? promotes; + private FrozenDictionary availableTokens; /// /// 角色列表 @@ -75,9 +82,13 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); } /// - /// 筛选文本 + /// 保存的筛选标志 /// - public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); } + public ObservableCollection? FilterTokens { get => filterTokens; set => SetProperty(ref filterTokens, value); } + + public string? FilterToken { get => filterToken; set => SetProperty(ref filterToken, value); } + + public FrozenDictionary? AvailableTokens { get => availableTokens; } protected override async ValueTask InitializeUIAsync() { @@ -101,6 +112,18 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel await taskContext.SwitchToMainThreadAsync(); Avatars = new(list, true); Selected = Avatars.View.ElementAtOrDefault(0); + FilterTokens = []; + + availableTokens = FrozenDictionary.ToFrozenDictionary( + [ + .. avatars.Select(avatar => KeyValuePair.Create(avatar.Name, new SearchToken(SearchTokenKind.Avatar, avatar.Name, sideIconUri: AvatarSideIconConverter.IconNameToUri(avatar.SideIcon)))), + .. IntrinsicFrozen.AssociationTypes.Select(assoc => KeyValuePair.Create(assoc, new SearchToken(SearchTokenKind.AssociationType, assoc, iconUri: AssociationTypeIconConverter.AssociationTypeNameToIconUri(assoc)))), + .. IntrinsicFrozen.BodyTypes.Select(b => KeyValuePair.Create(b, new SearchToken(SearchTokenKind.BodyType, b))), + .. IntrinsicFrozen.ElementNames.Select(e => KeyValuePair.Create(e, new SearchToken(SearchTokenKind.ElementName, e, iconUri: ElementNameIconConverter.ElementNameToIconUri(e)))), + .. IntrinsicFrozen.ItemQualities.Select(i => KeyValuePair.Create(i, new SearchToken(SearchTokenKind.ItemQuality, i, quality: QualityColorConverter.QualityNameToColor(i)))), + .. IntrinsicFrozen.WeaponTypes.Select(w => KeyValuePair.Create(w, new SearchToken(SearchTokenKind.WeaponType, w, iconUri: WeaponTypeIconConverter.WeaponTypeNameToIconUri(w)))), + ]); + return true; } @@ -203,20 +226,20 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel } [Command("FilterCommand")] - private void ApplyFilter(string? input) + private void ApplyFilter() { if (Avatars is null) { return; } - if (string.IsNullOrWhiteSpace(input)) + if (FilterTokens.IsNullOrEmpty()) { Avatars.Filter = default!; return; } - Avatars.Filter = AvatarFilter.Compile(input); + Avatars.Filter = AvatarFilter.Compile(FilterTokens); 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..6816423d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -1,12 +1,15 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Control.AutoSuggestBox; 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.Converter; using Snap.Hutao.Model.Metadata.Item; using Snap.Hutao.Model.Metadata.Weapon; using Snap.Hutao.Model.Primitive; @@ -17,6 +20,8 @@ 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.Collections.ObjectModel; using System.Runtime.InteropServices; using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; @@ -42,10 +47,12 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel private AdvancedCollectionView? weapons; private Weapon? selected; - private string? filterText; + private ObservableCollection? filterTokens; + private string? filterToken; private BaseValueInfo? baseValueInfo; private Dictionary>? levelWeaponCurveMap; private List? promotes; + private FrozenDictionary availableTokens; /// /// 角色列表 @@ -72,9 +79,13 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); } /// - /// 筛选文本 + /// 保存的筛选标志 /// - public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); } + public ObservableCollection? FilterTokens { get => filterTokens; set => SetProperty(ref filterTokens, value); } + + public string? FilterToken { get => filterToken; set => SetProperty(ref filterToken, value); } + + public FrozenDictionary? AvailableTokens { get => availableTokens; } /// protected override async Task OpenUIAsync() @@ -98,6 +109,15 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel Weapons = new(list, true); Selected = Weapons.View.ElementAtOrDefault(0); + FilterTokens = []; + + availableTokens = FrozenDictionary.ToFrozenDictionary( + [ + .. weapons.Select(w => KeyValuePair.Create(w.Name, new SearchToken(SearchTokenKind.Weapon, w.Name, sideIconUri: EquipIconConverter.IconNameToUri(w.Icon)))), + .. IntrinsicFrozen.FightProperties.Select(f => KeyValuePair.Create(f, new SearchToken(SearchTokenKind.FightProperty, f))), + .. IntrinsicFrozen.ItemQualities.Select(i => KeyValuePair.Create(i, new SearchToken(SearchTokenKind.ItemQuality, i, quality: QualityColorConverter.QualityNameToColor(i)))), + .. IntrinsicFrozen.WeaponTypes.Select(w => KeyValuePair.Create(w, new SearchToken(SearchTokenKind.WeaponType, w, iconUri: WeaponTypeIconConverter.WeaponTypeNameToIconUri(w)))), + ]); } } @@ -188,20 +208,20 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel } [Command("FilterCommand")] - private void ApplyFilter(string? input) + private void ApplyFilter() { if (Weapons is null) { return; } - if (string.IsNullOrWhiteSpace(input)) + if (FilterTokens.IsNullOrEmpty()) { Weapons.Filter = default!; return; } - Weapons.Filter = WeaponFilter.Compile(input); + Weapons.Filter = WeaponFilter.Compile(FilterTokens); if (Selected is not null && Weapons.Contains(Selected)) { diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs index ed0b1d08..d521d199 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs @@ -47,21 +47,22 @@ internal static class HoyolabOptions /// /// 盐 /// - public static FrozenDictionary Salts { get; } = new Dictionary() - { + public static FrozenDictionary Salts { get; } = FrozenDictionary.ToFrozenDictionary( + [ + // Chinese - [SaltType.K2] = SaltConstants.CNK2, - [SaltType.LK2] = SaltConstants.CNLK2, - [SaltType.X4] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs", - [SaltType.X6] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v", - [SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS", + KeyValuePair.Create(SaltType.K2, SaltConstants.CNK2), + KeyValuePair.Create(SaltType.LK2, SaltConstants.CNLK2), + KeyValuePair.Create(SaltType.X4, "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs"), + KeyValuePair.Create(SaltType.X6, "t0qEgfub6cvueAPgR5m9aQWWVciEer7v"), + KeyValuePair.Create(SaltType.PROD, "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS"), // Oversea - [SaltType.OSK2] = SaltConstants.OSK2, - [SaltType.OSLK2] = SaltConstants.OSLK2, - [SaltType.OSX4] = "h4c1d6ywfq5bsbnbhm1bzq7bxzzv6srt", - [SaltType.OSX6] = "okr4obncj8bw5a65hbnn5oo6ixjc3l9w", - }.ToFrozenDictionary(); + KeyValuePair.Create(SaltType.OSK2, SaltConstants.OSK2), + KeyValuePair.Create(SaltType.OSLK2, SaltConstants.OSLK2), + KeyValuePair.Create(SaltType.OSX4, "h4c1d6ywfq5bsbnbhm1bzq7bxzzv6srt"), + KeyValuePair.Create(SaltType.OSX6, "okr4obncj8bw5a65hbnn5oo6ixjc3l9w"), + ]); [SuppressMessage("", "CA1308")] private static string GenerateDeviceId40()