Compare commits

...

53 Commits

Author SHA1 Message Date
qhy040404
e48ce1dd3b add delay to fix blink again 2024-03-08 18:41:22 +08:00
DismissedLight
1c847626c7 Merge pull request #1453 from Mikachu2333/develop 2024-03-08 16:12:08 +08:00
LinkChou
062a09c632 correct 2024-03-08 12:16:55 +08:00
LinkChou
7433c1832a add tips about 'test binary package' 2024-03-07 19:41:42 +08:00
Lightczx
252649f28c fix #1429 2024-03-07 16:56:18 +08:00
Lightczx
75dda65c55 impl #1432 2024-03-07 16:53:25 +08:00
Lightczx
a9b165882f fix #1433 2024-03-07 16:49:15 +08:00
Lightczx
e41e913558 code style 2024-03-07 16:44:06 +08:00
Lightczx
2d9125d369 fix #1443 2024-03-07 16:37:26 +08:00
DismissedLight
2c7a6d152c Merge pull request #1445 from DGP-Studio/feat/1434 2024-03-07 16:27:54 +08:00
Lightczx
0c4c509fd6 code style 2024-03-07 16:27:01 +08:00
DismissedLight
7dba08451c Merge branch 'develop' into feat/1434 2024-03-07 16:20:48 +08:00
DismissedLight
a55cb89fe8 Merge pull request #1452 from DGP-Studio/fix/layoutcycle_2 2024-03-07 16:20:17 +08:00
Lightczx
4aa9557526 code style 2024-03-07 16:20:10 +08:00
qhy040404
38577f9813 apply suggestion 2024-03-07 13:37:22 +08:00
qhy040404
91361ddab5 resolve review 2024-03-07 13:26:35 +08:00
qhy040404
5ced8f6c71 impl #1434 2024-03-07 13:26:35 +08:00
qhy040404
55706e68f0 use self impl EqualPanel to fix layout cycle 2024-03-07 13:25:03 +08:00
qhy040404
63c4bb6a23 fix command bar blink 2024-03-07 13:00:52 +08:00
Lightczx
baa4b88622 refine spiralabyss team ui 2024-03-06 17:11:23 +08:00
DismissedLight
de2ce0db63 Merge pull request #1444 from DGP-Studio/feat/refresh_ui_for_team_appearence 2024-03-06 15:47:45 +08:00
Lightczx
6cbf8ca918 code style 2024-03-06 15:43:46 +08:00
DismissedLight
df125904a6 Merge branch 'develop' into feat/refresh_ui_for_team_appearence 2024-03-06 14:20:36 +08:00
Lightczx
1bad9d0928 fix #1449 2024-03-06 14:19:04 +08:00
DismissedLight
a762166db4 Merge branch 'develop' into feat/refresh_ui_for_team_appearence 2024-03-06 14:09:23 +08:00
DismissedLight
f432e5ba42 Merge pull request #1437 from DGP-Studio/activity-city-banner-compat 2024-03-06 14:07:47 +08:00
Lightczx
46dd4db25c fixup endid indexer setter 2024-03-06 14:04:04 +08:00
Lightczx
2e685182e6 fixup endid indexer getter 2024-03-06 14:01:53 +08:00
Lightczx
4f1bbd2e89 typo fix 2024-03-06 13:58:34 +08:00
qhy040404
0f5b6dda16 refresh ui for team appearance 2024-03-06 12:02:56 +08:00
Lightczx
4c38bb528f basic support 2024-03-06 11:57:37 +08:00
Lightczx
44e7f7482c minor adjustment 2024-03-06 11:55:37 +08:00
Lightczx
8a2fa3c701 factory completed 2024-03-06 11:55:37 +08:00
DismissedLight
6e10819609 Merge pull request #1441 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-15667d2017 2024-03-06 09:16:27 +08:00
dependabot[bot]
c1b838bd97 Bump the packages group in /src/Snap.Hutao with 2 updates
Bumps the packages group in /src/Snap.Hutao with 2 updates: [MSTest.TestAdapter](https://github.com/microsoft/testfx) and [MSTest.TestFramework](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.2.1 to 3.2.2
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.1...v3.2.2)

Updates `MSTest.TestFramework` from 3.2.1 to 3.2.2
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.1...v3.2.2)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-05 16:03:28 +00:00
DismissedLight
268c4c6c71 Merge pull request #1421 from DGP-Studio/feat/wikisuggestbox 2024-03-05 23:59:39 +08:00
DismissedLight
59dabaa5be code style 2024-03-05 23:59:23 +08:00
DismissedLight
dcac7ac792 Merge pull request #1431 from DGP-Studio/fix/minor 2024-03-05 21:45:00 +08:00
qhy040404
2ad6dad282 minor fix 2024-03-05 17:28:01 +08:00
qhy040404
4185336556 minor ui adjustment 2024-03-05 17:10:31 +08:00
qhy040404
15a627e50a init all tokens first 2024-03-05 16:55:28 +08:00
qhy040404
2ea53fd39d Frozen Dictionaries 2024-03-04 18:02:00 +08:00
qhy040404
6de3cba550 remove unused code 2024-03-04 00:09:38 +08:00
qhy040404
03c1bacfe9 maybe code style 2024-03-04 00:01:52 +08:00
qhy040404
d157476a9a minor fix 2024-03-03 21:37:44 +08:00
qhy040404
e35c03ac90 support or in wiki search 2024-03-03 21:03:28 +08:00
qhy040404
9619835cc2 migrate to TokenizingTextBox 2024-03-03 16:23:25 +08:00
qhy040404
6936a30a74 finish suggestion methods 2024-03-03 16:23:19 +08:00
Lightczx
506d198167 update to was 1.5 2024-03-01 09:30:43 +08:00
DismissedLight
9c2131cb9d Merge pull request #1426 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-dcc47c4d6c 2024-02-27 09:21:58 +08:00
DismissedLight
896e3d49f2 Merge branch 'develop' into dependabot/nuget/src/Snap.Hutao/develop/packages-dcc47c4d6c 2024-02-27 09:21:30 +08:00
DismissedLight
7da24790c4 Merge pull request #1423 from Masterain98/main 2024-02-26 17:02:25 +08:00
Masterain
ad67001556 Update LaunchGame.png 2024-02-26 15:28:25 +08:00
110 changed files with 1597 additions and 788 deletions

View File

@@ -35,7 +35,8 @@ Install with Snap Hutao MSIX package, can be installed with Windows built-in App
* [向我们提交 PR / Make Pull Requests](https://github.com/DGP-Studio/Snap.Hutao/pulls)
* [在 Crowdin 上进行本地化 / Translate Project on Crowdin](https://translate.hut.ao/)
* [为我们更新文档 / Enhance our Document ](https://github.com/DGP-Studio/Snap.Hutao.Docs)
* [为我们更新文档 / Enhance our Document](https://github.com/DGP-Studio/Snap.Hutao.Docs)
* [帮助我们测试程序 / Test Binary Package](https://hut.ao/development/contribute.html)
## 特别感谢 / Special Thanks

View File

@@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -6,6 +6,7 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources/>
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.TokenizingTextBox/TokenizingTextBox.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Loading.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Image/CachedImage.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Card.xaml"/>
@@ -22,6 +23,7 @@
<ResourceDictionary Source="ms-appx:///Control/Theme/PageOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/PivotOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/ScrollViewer.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/SegmentedOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/SettingsStyle.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Thickness.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>

View File

@@ -0,0 +1,65 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.Control.AutoSuggestBox;
[DependencyProperty("FilterCommand", typeof(ICommand))]
[DependencyProperty("FilterCommandParameter", typeof(object))]
[DependencyProperty("AvailableTokens", typeof(IReadOnlyDictionary<string, SearchToken>))]
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);
}
}

View File

@@ -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;
}
}

View File

@@ -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,
}

View File

@@ -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<UIEle
return;
}
if (Command is not null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
executed = true;
}
executed = Command.TryExecute(CommandParameter);
}
}

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;
using Snap.Hutao.Control.Extension;
namespace Snap.Hutao.Control.Behavior;
@@ -49,10 +50,7 @@ internal sealed partial class PeriodicInvokeCommandOrOnActualThemeChangedBehavio
return;
}
if (Command is not null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}
Command.TryExecute(CommandParameter);
}
private async ValueTask RunCoreAsync()

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control.Extension;
internal static class CommandExtension
{
public static bool TryExecute(this ICommand? command, object? parameter = null)
{
if (command is not null && command.CanExecute(parameter))
{
command.Execute(parameter);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Helper;
[SuppressMessage("", "SH001")]
[DependencyProperty("VisibilityObject", typeof(object), null, nameof(OnVisibilityObjectChanged), IsAttached = true, AttachedType = typeof(UIElement))]
[DependencyProperty("OpacityObject", typeof(object), null, nameof(OnOpacityObjectChanged), IsAttached = true, AttachedType = typeof(UIElement))]
public sealed partial class UIElementHelper
{
private static void OnVisibilityObjectChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
UIElement element = (UIElement)dp;
element.Visibility = e.NewValue is null ? Visibility.Collapsed : Visibility.Visible;
}
private static void OnOpacityObjectChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
UIElement element = (UIElement)dp;
element.Opacity = e.NewValue is null ? 0D : 1D;
}
}

View File

@@ -168,6 +168,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
if (surface.DecodedPhysicalSize.Size() <= 0D)
{
await Task.WhenAny(surfaceLoadTaskCompletionSource.Task, Task.Delay(5000, token)).ConfigureAwait(true);
await Task.Delay(50, token).ConfigureAwait(true);
}
LoadImageSurfaceCompleted(surface);

View File

@@ -0,0 +1,89 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using System.Data;
using System.Runtime.InteropServices;
using Windows.Foundation;
namespace Snap.Hutao.Control.Panel;
[DependencyProperty("Spacing", typeof(double), default(double), nameof(OnSpacingChanged))]
internal partial class EqualPanel : Microsoft.UI.Xaml.Controls.Panel
{
private double maxItemWidth;
private double maxItemHeight;
private int visibleItemsCount;
public EqualPanel()
{
RegisterPropertyChangedCallback(HorizontalAlignmentProperty, OnHorizontalAlignmentChanged);
}
protected override Size MeasureOverride(Size availableSize)
{
maxItemWidth = 0;
maxItemHeight = 0;
List<UIElement> elements = [.. Children.Where(element => element.Visibility == Visibility.Visible)];
visibleItemsCount = elements.Count;
foreach (ref readonly UIElement child in CollectionsMarshal.AsSpan(elements))
{
child.Measure(availableSize);
maxItemWidth = Math.Max(maxItemWidth, child.DesiredSize.Width);
maxItemHeight = Math.Max(maxItemHeight, child.DesiredSize.Height);
}
if (visibleItemsCount > 0)
{
// Return equal widths based on the widest item
// In very specific edge cases the AvailableWidth might be infinite resulting in a crash.
if (HorizontalAlignment is not HorizontalAlignment.Stretch || double.IsInfinity(availableSize.Width))
{
return new Size((maxItemWidth * visibleItemsCount) + (Spacing * (visibleItemsCount - 1)), maxItemHeight);
}
else
{
// Equal columns based on the available width, adjust for spacing
double totalWidth = availableSize.Width - (Spacing * (visibleItemsCount - 1));
maxItemWidth = totalWidth / visibleItemsCount;
return new Size(availableSize.Width, maxItemHeight);
}
}
else
{
return new Size(0, 0);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
double x = 0;
// Check if there's more (little) width available - if so, set max item width to the maximum possible as we have an almost perfect height.
if (finalSize.Width > (visibleItemsCount * maxItemWidth) + (Spacing * (visibleItemsCount - 1)))
{
maxItemWidth = (finalSize.Width - (Spacing * (visibleItemsCount - 1))) / visibleItemsCount;
}
IEnumerable<UIElement> elements = Children.Where(static e => e.Visibility == Visibility.Visible);
foreach (UIElement child in elements)
{
child.Arrange(new Rect(x, 0, maxItemWidth, maxItemHeight));
x += maxItemWidth + Spacing;
}
return finalSize;
}
private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as EqualPanel)?.InvalidateMeasure();
}
private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp)
{
InvalidateMeasure();
}
}

View File

@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
Style="{StaticResource DefaultSegmentedStyle}"
mc:Ignorable="d">
<cwc:SegmentedItem

View File

@@ -4,6 +4,7 @@
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Setting;
using System.Collections.Frozen;
namespace Snap.Hutao.Control.Panel;
@@ -19,11 +20,11 @@ internal sealed partial class PanelSelector : Segmented
public const string List = nameof(List);
public const string Grid = nameof(Grid);
private static readonly Dictionary<int, string> IndexTypeMap = new()
{
[0] = List,
[1] = Grid,
};
private static readonly FrozenDictionary<int, string> IndexTypeMap = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(0, List),
KeyValuePair.Create(1, Grid),
]);
private readonly RoutedEventHandler loadedEventHandler;
private readonly RoutedEventHandler unloadedEventHandler;

View File

@@ -17,6 +17,9 @@
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing2Template">
<StackPanel Orientation="Horizontal" Spacing="2"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing4Template">
<StackPanel Orientation="Horizontal" Spacing="4"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="StackPanelSpacing4Template">
<StackPanel Spacing="4"/>
</ItemsPanelTemplate>

View File

@@ -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);
}

View File

@@ -0,0 +1,115 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cw="using:CommunityToolkit.WinUI"
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
xmlns:shcp="using:Snap.Hutao.Control.Panel"
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/SegmentedItem/SegmentedItem.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="SegmentedBackground" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedBorderBrush" ResourceKey="ControlStrokeColorDefaultBrush"/>
<Thickness x:Key="SegmentedBorderThickness">1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="SegmentedBackground" ResourceKey="ControlAltFillColorSecondaryBrush"/>
<StaticResource x:Key="SegmentedBorderBrush" ResourceKey="ControlStrokeColorDefaultBrush"/>
<Thickness x:Key="SegmentedBorderThickness">1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="SegmentedBackground" ResourceKey="SystemColorButtonFaceColor"/>
<StaticResource x:Key="SegmentedBorderBrush" ResourceKey="SystemColorHighlightColorBrush"/>
<Thickness x:Key="SegmentedBorderThickness">1</Thickness>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="SegmentedItemSpacing">1</x:Double>
<x:Double x:Key="ButtonItemSpacing">2</x:Double>
<Style BasedOn="{StaticResource DefaultSegmentedStyle}" TargetType="cwc:Segmented"/>
<Style x:Key="DefaultSegmentedStyle" TargetType="cwc:Segmented">
<Style.Setters>
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
<Setter Property="Background" Value="{ThemeResource SegmentedBackground}"/>
<Setter Property="BorderBrush" Value="{ThemeResource SegmentedBorderBrush}"/>
<Setter Property="BorderThickness" Value="{ThemeResource SegmentedBorderThickness}"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="IsItemClickEnabled" Value="False"/>
<win:Setter Property="SingleSelectionFollowsFocus" Value="False"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="TabNavigation" Value="Once"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<shcp:EqualPanel
HorizontalAlignment="{Binding (cw:FrameworkElementExtensions.Ancestor).HorizontalAlignment, RelativeSource={RelativeSource Self}}"
cw:FrameworkElementExtensions.AncestorType="cwc:Segmented"
Spacing="{ThemeResource SegmentedItemSpacing}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="cwc:Segmented">
<Grid>
<Border
VerticalAlignment="Stretch"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"/>
<ItemsPresenter Margin="{TemplateBinding Padding}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
<Style
x:Key="PivotSegmentedStyle"
BasedOn="{StaticResource DefaultSegmentedStyle}"
TargetType="cwc:Segmented">
<Style.Setters>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource PivotSegmentedItemStyle}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="{ThemeResource SegmentedItemSpacing}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
<Style
x:Key="ButtonSegmentedStyle"
BasedOn="{StaticResource DefaultSegmentedStyle}"
TargetType="cwc:Segmented">
<Style.Setters>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource ButtonSegmentedItemStyle}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="{ThemeResource ButtonItemSpacing}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ResourceDictionary>

View File

@@ -26,12 +26,12 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
{
private const string CacheFolderName = nameof(ImageCache);
private readonly FrozenDictionary<int, TimeSpan> retryCountToDelay = new Dictionary<int, TimeSpan>()
{
[0] = TimeSpan.FromSeconds(4),
[1] = TimeSpan.FromSeconds(16),
[2] = TimeSpan.FromSeconds(64),
}.ToFrozenDictionary();
private readonly FrozenDictionary<int, TimeSpan> retryCountToDelay = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(0, TimeSpan.FromSeconds(4)),
KeyValuePair.Create(1, TimeSpan.FromSeconds(16)),
KeyValuePair.Create(2, TimeSpan.FromSeconds(64)),
]);
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();

View File

@@ -23,8 +23,16 @@ internal sealed partial class ExceptionRecorder
public void Record(Application app)
{
app.UnhandledException += OnAppUnhandledException;
app.DebugSettings.FailFastOnErrors = false;
app.DebugSettings.IsBindingTracingEnabled = true;
app.DebugSettings.BindingFailed += OnXamlBindingFailed;
app.DebugSettings.IsXamlResourceReferenceTracingEnabled = true;
app.DebugSettings.XamlResourceReferenceFailed += OnXamlResourceReferenceFailed;
app.DebugSettings.LayoutCycleTracingLevel = LayoutCycleTracingLevel.High;
app.DebugSettings.LayoutCycleDebugBreakLevel = LayoutCycleDebugBreakLevel.High;
}
[SuppressMessage("", "CA2012")]

View File

@@ -37,4 +37,10 @@ internal sealed class HutaoException : Exception
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
throw new HutaoException(HutaoExceptionKind.ServiceTypeCastFailed, message, innerException);
}
public static HutaoException GachaStatisticsInvalidItemId(uint id, Exception? innerException = default)
{
string message = SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id);
throw new HutaoException(HutaoExceptionKind.GachaStatisticsInvalidItemId, message, innerException);
}
}

View File

@@ -9,4 +9,5 @@ internal enum HutaoExceptionKind
ServiceTypeCastFailed,
FileSystemCreateFileInsufficientPermissions,
PrivateNamedPipeContentHashIncorrect,
GachaStatisticsInvalidItemId,
}

View File

@@ -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<Type, string> 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<Type, string> 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"),
]);
/// <summary>
/// 获取对象类型的显示名称

View File

@@ -122,13 +122,17 @@ internal sealed class WindowController
private void OnOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(AppOptions.BackdropType))
if (sender is not AppOptions options)
{
if (sender is AppOptions options)
{
UpdateSystemBackdrop(options.BackdropType);
}
return;
}
_ = e.PropertyName switch
{
nameof(AppOptions.BackdropType) => UpdateSystemBackdrop(options.BackdropType),
nameof(AppOptions.ElementTheme) => UpdateElementTheme(options.ElementTheme),
_ => false,
};
}
private void OnWindowClosed(object sender, WindowEventArgs args)
@@ -158,7 +162,7 @@ internal sealed class WindowController
}
}
private void UpdateSystemBackdrop(BackdropType backdropType)
private bool UpdateSystemBackdrop(BackdropType backdropType)
{
window.SystemBackdrop = backdropType switch
{
@@ -168,6 +172,15 @@ internal sealed class WindowController
BackdropType.Acrylic => new DesktopAcrylicBackdrop(),
_ => null,
};
return true;
}
private bool UpdateElementTheme(ElementTheme theme)
{
((FrameworkElement)window.Content).RequestedTheme = theme;
return true;
}
private void UpdateTitleButtonColor()

View File

@@ -15,6 +15,12 @@ namespace Snap.Hutao.Extension;
[HighQuality]
internal static partial class EnumerableExtension
{
public static void Deconstruct<TKey, TElement>(this IGrouping<TKey, TElement> grouping, out TKey key, out IEnumerable<TElement> elements)
{
key = grouping.Key;
elements = grouping;
}
public static TElement? ElementAtOrLastOrDefault<TElement>(this IEnumerable<TElement> source, int index)
{
return source.ElementAtOrDefault(index) ?? source.LastOrDefault();

View File

@@ -42,14 +42,14 @@ internal sealed partial class GachaItem
/// <summary>
/// 祈愿记录分类
/// </summary>
public GachaConfigType GachaType { get; set; }
public GachaType GachaType { get; set; }
/// <summary>
/// 祈愿记录查询分类
/// 合并保底的卡池使用此属性
/// 仅4种不含400
/// </summary>
public GachaConfigType QueryType { get; set; }
public GachaType QueryType { get; set; }
/// <summary>
/// 物品Id

View File

@@ -13,6 +13,7 @@ internal sealed partial class SettingEntry
public const string Culture = "Culture";
public const string SystemBackdropType = "SystemBackdropType";
public const string ElementTheme = "ElementTheme";
public const string BackgroundImageType = "BackgroundImageType";
public const string AnnouncementRegion = "AnnouncementRegion";

View File

@@ -20,7 +20,7 @@ internal sealed class UIGFItem : GachaLogItem, IMappingFrom<UIGFItem, GachaItem,
/// </summary>
[JsonPropertyName("uigf_gacha_type")]
[JsonEnum(JsonSerializeType.NumberString)]
public GachaConfigType UIGFGachaType { get; set; } = default!;
public GachaType UIGFGachaType { get; set; } = default!;
public static UIGFItem From(GachaItem item, INameQuality nameQuality)
{

View File

@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Achievement;
/// <summary>

View File

@@ -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<AssociationType, Uri?>
{
private static readonly FrozenDictionary<string, AssociationType> 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);
}
}

View File

@@ -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<string, Uri>
{
private static readonly Dictionary<string, string> 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<string, string> 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<string, ElementType> 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<string, ElementType> 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),
]);
/// <summary>
/// 将中文元素名称转换为图标链接

View File

@@ -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<QualityType, Color>
{
private static readonly FrozenDictionary<string, QualityType> 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<QualityType, Color> 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);
}
/// <inheritdoc/>
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);
}
}

View File

@@ -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<WeaponType, Uri>
{
private static readonly FrozenDictionary<string, WeaponType> 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));
}
/// <summary>
/// 将武器类型转换为图标链接
/// </summary>

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Furniture;

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Furniture;

View File

@@ -49,7 +49,7 @@ internal sealed class GachaEvent
/// <summary>
/// 卡池类型
/// </summary>
public GachaConfigType Type { get; set; }
public GachaType Type { get; set; }
/// <summary>
/// 五星列表

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata;

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -186,6 +186,15 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>No WebView2 Runtime detected</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>Dark</value>
</data>
<data name="CoreWindowThemeLight" xml:space="preserve">
<value>Light</value>
</data>
<data name="CoreWindowThemeSystem" xml:space="preserve">
<value>System</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>Export</value>
</data>

View File

@@ -189,6 +189,15 @@
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] 热键 [{1}] 注册失败</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>深色</value>
</data>
<data name="CoreWindowThemeLight" xml:space="preserve">
<value>浅色</value>
</data>
<data name="CoreWindowThemeSystem" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>导出</value>
</data>
@@ -860,6 +869,9 @@
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>角色活动</value>
</data>
<data name="ServiceGachaLogFactoryChronicledWishName" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="ServiceGachaLogFactoryPermanentWishName" xml:space="preserve">
<value>奔行世间</value>
</data>
@@ -2217,7 +2229,7 @@
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
<value>自动化任务</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>常规</value>
@@ -2235,7 +2247,7 @@
<value>文件</value>
</data>
<data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve">
<value>进程</value>
<value>进程联动</value>
</data>
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
<value>在指定的显示器上运行</value>
@@ -2624,6 +2636,12 @@
<data name="ViewPageSettingStoreReviewNavigate" xml:space="preserve">
<value>评价软件</value>
</data>
<data name="ViewPageSettingThemeDescription" xml:space="preserve">
<value>更改窗体的颜色主题</value>
</data>
<data name="ViewPageSettingThemeHeader" xml:space="preserve">
<value>颜色主题</value>
</data>
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>贡献翻译</value>
</data>
@@ -2633,6 +2651,12 @@
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>Webview2 运行时</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceDownHeader" xml:space="preserve">
<value>下半</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceUpHeader" xml:space="preserve">
<value>上半</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>搭配圣遗物</value>
</data>
@@ -3047,6 +3071,9 @@
<data name="WebGachaConfigTypeAvatarEventWish2" xml:space="preserve">
<value>角色活动祈愿-2</value>
</data>
<data name="WebGachaConfigTypeChronicledWish" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="WebGachaConfigTypeNoviceWish" xml:space="preserve">
<value>新手祈愿</value>
</data>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
@@ -16,6 +17,7 @@ internal sealed partial class AppOptions : DbStoreOptions
{
private bool? isEmptyHistoryWishVisible;
private BackdropType? backdropType;
private ElementTheme? elementTheme;
private BackgroundImageType? backgroundImageType;
private Region? region;
private string? geetestCustomCompositeUrl;
@@ -34,6 +36,19 @@ internal sealed partial class AppOptions : DbStoreOptions
set => SetOption(ref backdropType, SettingEntry.SystemBackdropType, value, EnumToStringOrEmpty);
}
public Lazy<List<NameValue<ElementTheme>>> LazyElementThemes { get; } = new(() =>
[
new(SH.CoreWindowThemeLight, ElementTheme.Light),
new(SH.CoreWindowThemeDark, ElementTheme.Dark),
new(SH.CoreWindowThemeSystem, ElementTheme.Default),
]);
public ElementTheme ElementTheme
{
get => GetOption(ref elementTheme, SettingEntry.ElementTheme, EnumParse<ElementTheme>, ElementTheme.Default).Value;
set => SetOption(ref elementTheme, SettingEntry.ElementTheme, value, EnumToStringOrEmpty);
}
public List<NameValue<BackgroundImageType>> BackgroundImageTypes { get; } = CollectionsNameValue.FromEnum<BackgroundImageType>(type => type.GetLocalizedDescription());
public BackgroundImageType BackgroundImageType

View File

@@ -37,7 +37,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
return new(true, default!);
}
string path = System.Random.Shared.GetItems([..backgroundSet], 1)[0];
string path = System.Random.Shared.GetItems([.. backgroundSet], 1)[0];
backgroundSet.Remove(path);
if (string.Equals(path, previous?.Path, StringComparison.OrdinalIgnoreCase))

View File

@@ -39,7 +39,7 @@ internal sealed partial class CultivationService : ICultivationService
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
List<InventoryItemView> results = [];
foreach (Material meta in context.EnumerateInventroyMaterial())
foreach (Material meta in context.EnumerateInventoryMaterial())
{
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id);
results.Add(new(entity, meta, saveCommand));

View File

@@ -41,39 +41,33 @@ internal sealed partial class DiscordService : IDiscordService, IDisposable
private bool IsSupported()
{
try
{
// Actually requires a discord client to be running on Windows platform.
// If not, discord core creation code will throw.
Process[] discordProcesses = Process.GetProcessesByName("Discord");
// Actually requires a discord client to be running on Windows platform.
// If not, discord core creation code will throw.
Process[] discordProcesses = Process.GetProcessesByName("Discord");
if (discordProcesses.Length <= 0)
if (discordProcesses.Length <= 0)
{
return false;
}
foreach (Process process in discordProcesses)
{
try
{
_ = process.Handle;
}
catch (Exception)
{
if (!isInitialized)
{
isInitialized = true;
infoBarService.Warning(SH.ServiceDiscordActivityElevationRequiredHint);
}
return false;
}
foreach (Process process in discordProcesses)
{
try
{
_ = process.Handle;
}
catch (Exception)
{
if (!isInitialized)
{
infoBarService.Warning(SH.ServiceDiscordActivityElevationRequiredHint);
}
return false;
}
}
return true;
}
finally
{
isInitialized = true;
}
return true;
}
}

View File

@@ -1,41 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using System.Collections.Frozen;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Service.GachaLog.Factory;
/// <summary>
/// 祈愿配置类型比较器
/// </summary>
internal sealed class GachaConfigTypeComparer : IComparer<GachaConfigType>
{
private static readonly Lazy<GachaConfigTypeComparer> LazyShared = new(() => new());
private static readonly FrozenDictionary<GachaConfigType, int> OrderMap = new Dictionary<GachaConfigType, int>()
{
[GachaConfigType.AvatarEventWish] = 0,
[GachaConfigType.AvatarEventWish2] = 1,
[GachaConfigType.WeaponEventWish] = 2,
[GachaConfigType.StandardWish] = 3,
[GachaConfigType.NoviceWish] = 4,
}.ToFrozenDictionary();
/// <summary>
/// 共享的比较器
/// </summary>
public static GachaConfigTypeComparer Shared { get => LazyShared.Value; }
/// <inheritdoc/>
public int Compare(GachaConfigType x, GachaConfigType y)
{
return OrderOf(x) - OrderOf(y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int OrderOf(GachaConfigType type)
{
return OrderMap.GetValueOrDefault(type, 0);
}
}

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.ViewModel.GachaLog;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using Windows.UI;
@@ -25,7 +26,7 @@ internal static class GachaStatisticsExtension
bool isPreviousUp = true;
// mark the IsGuarantee
foreach (SummaryItem item in summaryItems)
foreach (ref readonly SummaryItem item in CollectionsMarshal.AsSpan(summaryItems))
{
if (item.IsUp && (!isPreviousUp))
{
@@ -62,4 +63,4 @@ internal static class GachaStatisticsExtension
ReadOnlySpan<byte> codes = MD5.HashData(Encoding.UTF8.GetBytes(name));
return Color.FromArgb(255, codes.Slice(0, 5).Average(), codes.Slice(5, 5).Average(), codes.Slice(10, 5).Average());
}
}
}

View File

@@ -3,10 +3,10 @@
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog;
@@ -31,9 +31,8 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
public async ValueTask<GachaStatistics> CreateAsync(List<Model.Entity.GachaItem> items, GachaLogServiceMetadataContext context)
{
await taskContext.SwitchToBackgroundAsync();
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventListAsync().ConfigureAwait(false);
List<HistoryWishBuilder> historyWishBuilders = gachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
List<HistoryWishBuilder> historyWishBuilders = context.GachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);
}
@@ -54,6 +53,9 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
TypedWishSummaryBuilderContext weaponContext = TypedWishSummaryBuilderContext.WeaponEventWish(taskContext, gachaLogClient);
TypedWishSummaryBuilder weaponWishBuilder = new(weaponContext);
TypedWishSummaryBuilderContext chronicledContext = TypedWishSummaryBuilderContext.ChronicledWish(taskContext, gachaLogClient);
TypedWishSummaryBuilder chronicledWishBuilder = new(chronicledContext);
Dictionary<Avatar, int> orangeAvatarCounter = [];
Dictionary<Avatar, int> purpleAvatarCounter = [];
Dictionary<Weapon, int> orangeWeaponCounter = [];
@@ -61,24 +63,25 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
Dictionary<Weapon, int> blueWeaponCounter = [];
// Pre group builders
Dictionary<GachaConfigType, List<HistoryWishBuilder>> historyWishBuilderMap = historyWishBuilders
Dictionary<GachaType, List<HistoryWishBuilder>> historyWishBuilderMap = historyWishBuilders
.GroupBy(b => b.ConfigType)
.ToDictionary(g => g.Key, g => g.ToList().SortBy(b => b.From));
// Items are ordered by precise time, first is oldest
// 'ref' is not allowed here because we have lambda below
foreach (Model.Entity.GachaItem item in CollectionsMarshal.AsSpan(items))
foreach (ref readonly Model.Entity.GachaItem item in CollectionsMarshal.AsSpan(items))
{
// Find target history wish to operate. // w.From <= item.Time <= w.To
HistoryWishBuilder? targetHistoryWishBuilder = item.GachaType is not (GachaConfigType.StandardWish or GachaConfigType.NoviceWish)
? historyWishBuilderMap[item.GachaType].BinarySearch(w => item.Time < w.From ? -1 : item.Time > w.To ? 1 : 0)
// Find target history wish to operate. // banner.From <= item.Time <= banner.To
Model.Entity.GachaItem pinned = item;
HistoryWishBuilder? targetHistoryWishBuilder = item.GachaType is not (GachaType.Standard or GachaType.NewBie)
? historyWishBuilderMap[item.GachaType].BinarySearch(banner => pinned.Time < banner.From ? -1 : pinned.Time > banner.To ? 1 : 0)
: default;
switch (item.ItemId.StringLength())
{
case 8U:
{
Avatar avatar = context.IdAvatarMap[item.ItemId];
Avatar avatar = context.GetAvatar(item.ItemId);
bool isUp = false;
switch (avatar.Quality)
@@ -98,6 +101,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
standardWishBuilder.Track(item, avatar, isUp);
avatarWishBuilder.Track(item, avatar, isUp);
weaponWishBuilder.Track(item, avatar, isUp);
chronicledWishBuilder.Track(item, avatar, isUp);
break;
}
@@ -127,17 +131,18 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
standardWishBuilder.Track(item, weapon, isUp);
avatarWishBuilder.Track(item, weapon, isUp);
weaponWishBuilder.Track(item, weapon, isUp);
chronicledWishBuilder.Track(item, weapon, isUp);
break;
}
default:
// ItemId string length not correct.
ThrowHelper.UserdataCorrupted(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(item.ItemId), default!);
HutaoException.GachaStatisticsInvalidItemId(item.ItemId);
break;
}
}
AsyncBarrier barrier = new(3);
AsyncBarrier barrier = new(4);
return new()
{
@@ -145,7 +150,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
HistoryWishes = historyWishBuilders
.Where(b => isEmptyHistoryWishVisible || (!b.IsEmpty))
.OrderByDescending(builder => builder.From)
.ThenBy(builder => builder.ConfigType, GachaConfigTypeComparer.Shared)
.ThenBy(builder => builder.ConfigType, GachaTypeComparer.Shared)
.Select(builder => builder.ToHistoryWish())
.ToList(),
@@ -162,6 +167,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
StandardWish = standardWishBuilder.ToTypedWishSummary(barrier),
AvatarWish = avatarWishBuilder.ToTypedWishSummary(barrier),
WeaponWish = weaponWishBuilder.ToTypedWishSummary(barrier),
ChronicledWish = chronicledWishBuilder.ToTypedWishSummary(barrier),
};
}
}

View File

@@ -62,22 +62,29 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
int weaponPurpleTracker = 0;
TypedWishSummarySlim weaponWish = new(SH.ServiceGachaLogFactoryWeaponWishName, 80, 10);
int chronicledOrangeTracker = 0;
int chronicledPurpleTracker = 0;
TypedWishSummarySlim chronicledWish = new(SH.ServiceGachaLogFactoryChronicledWishName, 90, 10);
// O(n) operation
foreach (ref readonly GachaItem item in CollectionsMarshal.AsSpan(items))
{
INameQuality nameQuality = context.GetNameQualityByItemId(item.ItemId);
switch (item.QueryType)
{
case GachaConfigType.StandardWish:
case GachaType.Standard:
Track(nameQuality, ref standardOrangeTracker, ref standardPurpleTracker);
break;
case GachaConfigType.AvatarEventWish:
case GachaConfigType.AvatarEventWish2:
case GachaType.ActivityAvatar:
case GachaType.SpecialActivityAvatar:
Track(nameQuality, ref avatarOrangeTracker, ref avatarPurpleTracker);
break;
case GachaConfigType.WeaponEventWish:
case GachaType.ActivityWeapon:
Track(nameQuality, ref weaponOrangeTracker, ref weaponPurpleTracker);
break;
case GachaType.ActivityCity:
Track(nameQuality, ref chronicledOrangeTracker, ref chronicledPurpleTracker);
break;
default:
break;
}
@@ -85,11 +92,16 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
standardWish.LastOrangePull = standardOrangeTracker;
standardWish.LastPurplePull = standardPurpleTracker;
avatarWish.LastOrangePull = avatarOrangeTracker;
avatarWish.LastPurplePull = avatarPurpleTracker;
weaponWish.LastOrangePull = weaponOrangeTracker;
weaponWish.LastPurplePull = weaponPurpleTracker;
chronicledWish.LastOrangePull = chronicledOrangeTracker;
chronicledWish.LastPurplePull = chronicledPurpleTracker;
return new()
{
Uid = uid,

View File

@@ -0,0 +1,42 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using System.Collections.Frozen;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Service.GachaLog.Factory;
/// <summary>
/// 祈愿配置类型比较器
/// </summary>
internal sealed class GachaTypeComparer : IComparer<GachaType>
{
private static readonly Lazy<GachaTypeComparer> LazyShared = new(() => new());
private static readonly FrozenDictionary<GachaType, int> OrderMap = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(GachaType.ActivityAvatar, 0),
KeyValuePair.Create(GachaType.SpecialActivityAvatar, 1),
KeyValuePair.Create(GachaType.ActivityWeapon, 2),
KeyValuePair.Create(GachaType.Standard, 3),
KeyValuePair.Create(GachaType.NewBie, 4),
KeyValuePair.Create(GachaType.ActivityCity, 5),
]);
/// <summary>
/// 共享的比较器
/// </summary>
public static GachaTypeComparer Shared { get => LazyShared.Value; }
/// <inheritdoc/>
public int Compare(GachaType x, GachaType y)
{
return OrderOf(x) - OrderOf(y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int OrderOf(GachaType type)
{
return OrderMap.GetValueOrDefault(type, 0);
}
}

View File

@@ -29,7 +29,6 @@ internal sealed class HistoryWishBuilder
/// </summary>
/// <param name="gachaEvent">卡池配置</param>
/// <param name="context">祈愿记录上下文</param>
[SuppressMessage("", "SH002")]
public HistoryWishBuilder(GachaEvent gachaEvent, GachaLogServiceMetadataContext context)
{
this.gachaEvent = gachaEvent;
@@ -37,21 +36,27 @@ internal sealed class HistoryWishBuilder
switch (ConfigType)
{
case GachaConfigType.AvatarEventWish or GachaConfigType.AvatarEventWish2:
case GachaType.ActivityAvatar or GachaType.SpecialActivityAvatar:
orangeUpCounter = gachaEvent.UpOrangeList.Select(id => context.IdAvatarMap[id]).ToDictionary(a => (IStatisticsItemSource)a, a => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(id => context.IdAvatarMap[id]).ToDictionary(a => (IStatisticsItemSource)a, a => 0);
break;
case GachaConfigType.WeaponEventWish:
case GachaType.ActivityWeapon:
orangeUpCounter = gachaEvent.UpOrangeList.Select(id => context.IdWeaponMap[id]).ToDictionary(w => (IStatisticsItemSource)w, w => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(id => context.IdWeaponMap[id]).ToDictionary(w => (IStatisticsItemSource)w, w => 0);
break;
case GachaType.ActivityCity:
// Avatars are less than weapons, so we try to get the value from avatar map first
orangeUpCounter = gachaEvent.UpOrangeList.Select(id => (IStatisticsItemSource?)context.IdAvatarMap.GetValueOrDefault(id) ?? context.IdWeaponMap[id]).ToDictionary(c => c, c => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(id => (IStatisticsItemSource?)context.IdAvatarMap.GetValueOrDefault(id) ?? context.IdWeaponMap[id]).ToDictionary(c => c, c => 0);
break;
}
}
/// <summary>
/// 祈愿配置类型
/// </summary>
public GachaConfigType ConfigType { get; }
public GachaType ConfigType { get; }
/// <inheritdoc cref="GachaEvent.From"/>
public DateTimeOffset From { get => gachaEvent.From; }
@@ -106,13 +111,13 @@ internal sealed class HistoryWishBuilder
{
HistoryWish historyWish = new()
{
// base
// Base
Name = gachaEvent.Name,
From = gachaEvent.From,
To = gachaEvent.To,
TotalCount = totalCountTracker,
// fill
// Fill
Version = gachaEvent.Version,
BannerImage = gachaEvent.Banner,
OrangeUpList = orangeUpCounter.ToStatisticsList(),

View File

@@ -5,6 +5,7 @@ using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog;
@@ -18,18 +19,20 @@ internal sealed class HutaoStatisticsFactory
private readonly GachaEvent avatarEvent;
private readonly GachaEvent avatarEvent2;
private readonly GachaEvent weaponEvent;
private readonly GachaEvent chronicledEvent;
public HutaoStatisticsFactory(in HutaoStatisticsFactoryMetadataContext context)
{
this.context = context;
// TODO: when in new verion
// when in new verion
// due to lack of newer metadata
// this can crash
DateTimeOffset now = DateTimeOffset.UtcNow;
avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish);
avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish2);
weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.WeaponEventWish);
avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityAvatar);
avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.SpecialActivityAvatar);
weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityWeapon);
chronicledEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaType.ActivityCity);
}
public HutaoStatistics Create(GachaEventStatistics raw)
@@ -38,7 +41,8 @@ internal sealed class HutaoStatisticsFactory
{
AvatarEvent = CreateWishSummary(avatarEvent, raw.AvatarEvent),
AvatarEvent2 = CreateWishSummary(avatarEvent2, raw.AvatarEvent2),
WeaponWish = CreateWishSummary(weaponEvent, raw.WeaponEvent),
WeaponEvent = CreateWishSummary(weaponEvent, raw.WeaponEvent),
Chronicled = CreateWishSummary(chronicledEvent, raw.Chronicled),
};
}
@@ -53,12 +57,13 @@ internal sealed class HutaoStatisticsFactory
{
IStatisticsItemSource source = item.Item.StringLength() switch
{
8U => context.IdAvatarMap[item.Item],
5U => context.IdWeaponMap[item.Item],
_ => throw ThrowHelper.UserdataCorrupted(SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(item.Item), default!),
8U => context.GetAvatar(item.Item),
5U => context.GetWeapon(item.Item),
_ => throw HutaoException.GachaStatisticsInvalidItemId(item.Item),
};
StatisticsItem statisticsItem = source.ToStatisticsItem(unchecked((int)item.Count));
// Put UP items to a separate list
if (gachaEvent.UpOrangeList.Contains(item.Item) || gachaEvent.UpPurpleList.Contains(item.Item))
{
upItems.Add(statisticsItem);

View File

@@ -5,19 +5,18 @@ using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
namespace Snap.Hutao.Service.GachaLog.Factory;
internal readonly struct HutaoStatisticsFactoryMetadataContext
internal sealed class HutaoStatisticsFactoryMetadataContext : IMetadataContext,
IMetadataDictionaryIdAvatarSource,
IMetadataDictionaryIdWeaponSource,
IMetadataListGachaEventSource
{
public readonly Dictionary<AvatarId, Avatar> IdAvatarMap;
public readonly Dictionary<WeaponId, Weapon> IdWeaponMap;
public readonly List<GachaEvent> GachaEvents;
public Dictionary<AvatarId, Avatar> IdAvatarMap { get; set; } = default!;
public HutaoStatisticsFactoryMetadataContext(Dictionary<AvatarId, Avatar> idAvatarMap, Dictionary<WeaponId, Weapon> idWeaponMap, List<GachaEvent> gachaEvents)
{
IdAvatarMap = idAvatarMap;
IdWeaponMap = idWeaponMap;
GachaEvents = gachaEvents;
}
public Dictionary<WeaponId, Weapon> IdWeaponMap { get; set; } = default!;
public List<GachaEvent> GachaEvents { get; set; } = default!;
}

View File

@@ -5,7 +5,6 @@ using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog.Factory;
@@ -15,21 +14,6 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
[HighQuality]
internal sealed class TypedWishSummaryBuilder
{
/// <summary>
/// 常驻祈愿
/// </summary>
public static readonly Func<GachaConfigType, bool> IsStandardWish = type => type is GachaConfigType.StandardWish;
/// <summary>
/// 角色活动
/// </summary>
public static readonly Func<GachaConfigType, bool> IsAvatarEventWish = type => type is GachaConfigType.AvatarEventWish or GachaConfigType.AvatarEventWish2;
/// <summary>
/// 武器活动
/// </summary>
public static readonly Func<GachaConfigType, bool> IsWeaponEventWish = type => type is GachaConfigType.WeaponEventWish;
private readonly TypedWishSummaryBuilderContext context;
private readonly List<int> averageOrangePullTracker = [];
@@ -62,52 +46,54 @@ internal sealed class TypedWishSummaryBuilder
/// <param name="isUp">是否为Up物品</param>
public void Track(GachaItem item, ISummaryItemSource source, bool isUp)
{
if (context.TypeEvaluator(item.GachaType))
if (!context.TypeEvaluator(item.GachaType))
{
++lastOrangePullTracker;
++lastPurplePullTracker;
++lastUpOrangePullTracker;
return;
}
// track total pulls
++totalCountTracker;
TrackFromToTime(item.Time);
++lastOrangePullTracker;
++lastPurplePullTracker;
++lastUpOrangePullTracker;
switch (source.Quality)
{
case QualityType.QUALITY_ORANGE:
// track total pulls
++totalCountTracker;
TrackFromToTime(item.Time);
switch (source.Quality)
{
case QualityType.QUALITY_ORANGE:
{
TrackMinMaxOrangePull(lastOrangePullTracker);
averageOrangePullTracker.Add(lastOrangePullTracker);
if (isUp)
{
TrackMinMaxOrangePull(lastOrangePullTracker);
averageOrangePullTracker.Add(lastOrangePullTracker);
if (isUp)
{
averageUpOrangePullTracker.Add(lastUpOrangePullTracker);
lastUpOrangePullTracker = 0;
}
summaryItems.Add(source.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
lastOrangePullTracker = 0;
++totalOrangePullTracker;
break;
averageUpOrangePullTracker.Add(lastUpOrangePullTracker);
lastUpOrangePullTracker = 0;
}
case QualityType.QUALITY_PURPLE:
{
lastPurplePullTracker = 0;
++totalPurplePullTracker;
break;
}
summaryItems.Add(source.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
case QualityType.QUALITY_BLUE:
{
++totalBluePullTracker;
break;
}
default:
lastOrangePullTracker = 0;
++totalOrangePullTracker;
break;
}
}
case QualityType.QUALITY_PURPLE:
{
lastPurplePullTracker = 0;
++totalPurplePullTracker;
break;
}
case QualityType.QUALITY_BLUE:
{
++totalBluePullTracker;
break;
}
default:
break;
}
}

View File

@@ -14,12 +14,13 @@ internal readonly struct TypedWishSummaryBuilderContext
public readonly string Name;
public readonly int GuaranteeOrangeThreshold;
public readonly int GuaranteePurpleThreshold;
public readonly Func<GachaConfigType, bool> TypeEvaluator;
public readonly Func<GachaType, bool> TypeEvaluator;
public readonly GachaDistributionType DistributionType;
private static readonly Func<GachaConfigType, bool> IsStandardWish = type => type is GachaConfigType.StandardWish;
private static readonly Func<GachaConfigType, bool> IsAvatarEventWish = type => type is GachaConfigType.AvatarEventWish or GachaConfigType.AvatarEventWish2;
private static readonly Func<GachaConfigType, bool> IsWeaponEventWish = type => type is GachaConfigType.WeaponEventWish;
private static readonly Func<GachaType, bool> IsStandardWish = type => type is GachaType.Standard;
private static readonly Func<GachaType, bool> IsAvatarEventWish = type => type is GachaType.ActivityAvatar or GachaType.SpecialActivityAvatar;
private static readonly Func<GachaType, bool> IsWeaponEventWish = type => type is GachaType.ActivityWeapon;
private static readonly Func<GachaType, bool> IsChronicledWish = type => type is GachaType.ActivityCity;
public TypedWishSummaryBuilderContext(
ITaskContext taskContext,
@@ -27,7 +28,7 @@ internal readonly struct TypedWishSummaryBuilderContext
string name,
int guaranteeOrangeThreshold,
int guaranteePurpleThreshold,
Func<GachaConfigType, bool> typeEvaluator,
Func<GachaType, bool> typeEvaluator,
GachaDistributionType distributionType)
{
TaskContext = taskContext;
@@ -54,6 +55,11 @@ internal readonly struct TypedWishSummaryBuilderContext
return new(taskContext, gachaLogClient, SH.ServiceGachaLogFactoryWeaponWishName, 80, 10, IsWeaponEventWish, GachaDistributionType.WeaponEvent);
}
public static TypedWishSummaryBuilderContext ChronicledWish(ITaskContext taskContext, HomaGachaLogClient gachaLogClient)
{
return new(taskContext, gachaLogClient, SH.ServiceGachaLogFactoryChronicledWishName, 90, 10, IsChronicledWish, GachaDistributionType.Chronicled);
}
public ValueTask<HutaoResponse<GachaDistribution>> GetGachaDistributionAsync()
{
return GachaLogClient.GetGachaDistributionAsync(DistributionType);

View File

@@ -15,12 +15,14 @@ internal static class GachaArchiveOperation
{
archive = archives.SingleOrDefault(a => a.Uid == uid);
if (archive is null)
if (archive is not null)
{
GachaArchive created = GachaArchive.From(uid);
gachaLogDbService.AddGachaArchive(created);
taskContext.InvokeOnMainThread(() => archives.Add(created));
archive = created;
return;
}
GachaArchive created = GachaArchive.From(uid);
gachaLogDbService.AddGachaArchive(created);
taskContext.InvokeOnMainThread(() => archives.Add(created));
archive = created;
}
}

View File

@@ -1,58 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog;
/// <summary>
/// 祈愿物品保存上下文
/// </summary>
internal readonly struct GachaItemSaveContext
{
/// <summary>
/// 待添加物品
/// </summary>
public readonly List<GachaItem> ItemsToAdd;
/// <summary>
/// 是否懒惰
/// </summary>
public readonly bool IsLazy;
public readonly GachaConfigType QueryType;
/// <summary>
/// 结尾 Id
/// </summary>
public readonly long EndId;
/// <summary>
/// 数据集
/// </summary>
public readonly IGachaLogDbService GachaLogDbService;
public GachaItemSaveContext(List<GachaItem> itemsToAdd, bool isLazy, GachaConfigType queryType, long endId, IGachaLogDbService gachaLogDbService)
{
ItemsToAdd = itemsToAdd;
IsLazy = isLazy;
QueryType = queryType;
EndId = endId;
GachaLogDbService = gachaLogDbService;
}
public void SaveItems(GachaArchive archive)
{
if (ItemsToAdd.Count > 0)
{
// 全量刷新
if (!IsLazy)
{
GachaLogDbService.RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(archive.InnerId, QueryType, EndId);
}
GachaLogDbService.AddGachaItemRange(ItemsToAdd);
}
}
}

View File

@@ -14,11 +14,12 @@ internal static class GachaLog
/// <summary>
/// 查询类型
/// </summary>
public static readonly FrozenSet<GachaConfigType> QueryTypes = FrozenSet.ToFrozenSet(
public static readonly FrozenSet<GachaType> QueryTypes = FrozenSet.ToFrozenSet(
[
GachaConfigType.NoviceWish,
GachaConfigType.StandardWish,
GachaConfigType.AvatarEventWish,
GachaConfigType.WeaponEventWish,
GachaType.NewBie,
GachaType.Standard,
GachaType.ActivityAvatar,
GachaType.ActivityWeapon,
GachaType.ActivityCity,
]);
}

View File

@@ -73,7 +73,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public async ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token)
public async ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaType queryType, CancellationToken token)
{
GachaItem? item = null;
@@ -103,7 +103,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? 0L;
}
public long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType)
public long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaType queryType)
{
GachaItem? item = null;
@@ -132,7 +132,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? 0L;
}
public async ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType)
public async ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaType queryType)
{
GachaItem? item = null;
@@ -205,7 +205,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? long.MaxValue;
}
public long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType)
public long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaType queryType)
{
GachaItem? item = null;
@@ -226,7 +226,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
return item?.Id ?? long.MaxValue;
}
public async ValueTask<long> GetOldestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token)
public async ValueTask<long> GetOldestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaType queryType, CancellationToken token)
{
GachaItem? item = null;
@@ -266,7 +266,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaConfigType queryType, long endId)
public List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
@@ -291,7 +291,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public async ValueTask<List<Web.Hutao.GachaLog.GachaItem>> GetHutaoGachaItemListAsync(Guid archiveId, GachaConfigType queryType, long endId)
public async ValueTask<List<Web.Hutao.GachaLog.GachaItem>> GetHutaoGachaItemListAsync(Guid archiveId, GachaType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
@@ -368,7 +368,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public void RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaConfigType queryType, long endId)
public void RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
@@ -381,7 +381,7 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
}
}
public async ValueTask RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndIdAsync(Guid archiveId, GachaConfigType queryType, long endId)
public async ValueTask RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndIdAsync(Guid archiveId, GachaType queryType, long endId)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{

View File

@@ -46,14 +46,14 @@ internal struct GachaLogFetchContext
/// <summary>
/// 当前类型
/// </summary>
public GachaConfigType CurrentType;
public GachaType CurrentType;
private readonly GachaLogServiceMetadataContext serviceContext;
private readonly IGachaLogDbService gachaLogDbService;
private readonly ITaskContext taskContext;
private readonly bool isLazy;
public GachaLogFetchContext(IGachaLogDbService gachaLogDbService, ITaskContext taskContext, in GachaLogServiceMetadataContext serviceContext, bool isLazy)
public GachaLogFetchContext(IGachaLogDbService gachaLogDbService, ITaskContext taskContext, GachaLogServiceMetadataContext serviceContext, bool isLazy)
{
this.gachaLogDbService = gachaLogDbService;
this.taskContext = taskContext;
@@ -66,7 +66,7 @@ internal struct GachaLogFetchContext
/// </summary>
/// <param name="configType">卡池类型</param>
/// <param name="query">查询</param>
public void ResetForProcessingType(GachaConfigType configType, in GachaLogQuery query)
public void ResetForProcessingType(GachaType configType, in GachaLogQuery query)
{
DbEndId = null;
CurrentType = configType;
@@ -140,8 +140,18 @@ internal struct GachaLogFetchContext
// While no item is fetched, archive can be null.
if (TargetArchive is not null)
{
GachaItemSaveContext saveContext = new(ItemsToAdd, isLazy, QueryOptions.Type, QueryOptions.EndId, gachaLogDbService);
saveContext.SaveItems(TargetArchive);
if (ItemsToAdd.Count <= 0)
{
return;
}
// 全量刷新
if (!isLazy)
{
gachaLogDbService.RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(TargetArchive.InnerId, QueryOptions.Type, QueryOptions.EndId);
}
gachaLogDbService.AddGachaItemRange(ItemsToAdd);
}
}

View File

@@ -6,33 +6,17 @@ using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog;
/// <summary>
/// 祈愿记录获取状态
/// </summary>
internal sealed class GachaLogFetchStatus
{
/// <summary>
/// 构造一个新的祈愿记录获取状态
/// </summary>
/// <param name="configType">卡池类型</param>
public GachaLogFetchStatus(GachaConfigType configType)
public GachaLogFetchStatus(GachaType configType)
{
ConfigType = configType;
}
/// <summary>
/// 验证密钥是否过期
/// </summary>
public bool AuthKeyTimeout { get; set; }
/// <summary>
/// 卡池类型
/// </summary>
public GachaConfigType ConfigType { get; set; }
public GachaType ConfigType { get; set; }
/// <summary>
/// 当前获取的物品
/// </summary>
public List<Item> Items { get; set; } = new(20);
public string Header

View File

@@ -2,12 +2,9 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.GachaLog.Factory;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog;
@@ -40,7 +37,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
if (await GetEndIdsFromCloudAsync(uid, token).ConfigureAwait(false) is { } endIds)
{
List<Web.Hutao.GachaLog.GachaItem> items = [];
foreach ((GachaConfigType type, long endId) in endIds)
foreach ((GachaType type, long endId) in endIds)
{
List<Web.Hutao.GachaLog.GachaItem> part = await gachaLogDbService
.GetHutaoGachaItemListAsync(gachaArchive.InnerId, type, endId)
@@ -55,14 +52,16 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
}
/// <inheritdoc/>
public async ValueTask<ValueResult<bool, Guid>> RetrieveGachaItemsAsync(string uid, CancellationToken token = default)
public async ValueTask<ValueResult<bool, Guid>> RetrieveGachaArchiveIdAsync(string uid, CancellationToken token = default)
{
GachaArchive? archive = await gachaLogDbService
.GetGachaArchiveByUidAsync(uid, token)
.ConfigureAwait(false);
EndIds endIds = await CreateEndIdsAsync(archive, token).ConfigureAwait(false);
Response<List<Web.Hutao.GachaLog.GachaItem>> resp = await homaGachaLogClient.RetrieveGachaItemsAsync(uid, endIds, token).ConfigureAwait(false);
Response<List<Web.Hutao.GachaLog.GachaItem>> resp = await homaGachaLogClient
.RetrieveGachaItemsAsync(uid, endIds, token)
.ConfigureAwait(false);
if (!resp.IsOk())
{
@@ -75,7 +74,8 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
await gachaLogDbService.AddGachaArchiveAsync(archive).ConfigureAwait(false);
}
List<Model.Entity.GachaItem> gachaItems = resp.Data.SelectList(i => Model.Entity.GachaItem.From(archive.InnerId, i));
Guid archiveId = archive.InnerId;
List<Model.Entity.GachaItem> gachaItems = resp.Data.SelectList(i => Model.Entity.GachaItem.From(archiveId, i));
await gachaLogDbService.AddGachaItemsAsync(gachaItems).ConfigureAwait(false);
return new(true, archive.InnerId);
}
@@ -94,10 +94,9 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
{
if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
Dictionary<WeaponId, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventListAsync(token).ConfigureAwait(false);
HutaoStatisticsFactoryMetadataContext context = new(idAvatarMap, idWeaponMap, gachaEvents);
HutaoStatisticsFactoryMetadataContext context = await metadataService
.GetContextAsync<HutaoStatisticsFactoryMetadataContext>(token)
.ConfigureAwait(false);
GachaEventStatistics raw = response.Data;
try
@@ -126,7 +125,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
private async ValueTask<EndIds> CreateEndIdsAsync(GachaArchive? archive, CancellationToken token)
{
EndIds endIds = new();
foreach (GachaConfigType type in GachaLog.QueryTypes)
foreach (GachaType type in GachaLog.QueryTypes)
{
if (archive is not null)
{

View File

@@ -5,10 +5,10 @@ using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.InterChange.GachaLog;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.GachaLog.Factory;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Response;
@@ -55,20 +55,14 @@ internal sealed partial class GachaLogService : IGachaLogService
/// <inheritdoc/>
public async ValueTask<bool> InitializeAsync(CancellationToken token = default)
{
if (context.IsInitialized)
if (context is { IsInitialized: true })
{
return true;
}
if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
Dictionary<string, Model.Metadata.Avatar.Avatar> nameAvatarMap = await metadataService.GetNameToAvatarMapAsync(token).ConfigureAwait(false);
Dictionary<string, Model.Metadata.Weapon.Weapon> nameWeaponMap = await metadataService.GetNameToWeaponMapAsync(token).ConfigureAwait(false);
context = new(idAvatarMap, idWeaponMap, nameAvatarMap, nameWeaponMap);
context = await metadataService.GetContextAsync<GachaLogServiceMetadataContext>(token).ConfigureAwait(false);
ArchiveCollection = gachaLogDbService.GetGachaArchiveCollection();
return true;
}
@@ -182,7 +176,7 @@ internal sealed partial class GachaLogService : IGachaLogService
ArgumentNullException.ThrowIfNull(ArchiveCollection);
GachaLogFetchContext fetchContext = new(gachaLogDbService, taskContext, context, isLazy);
foreach (GachaConfigType configType in GachaLog.QueryTypes)
foreach (GachaType configType in GachaLog.QueryTypes)
{
fetchContext.ResetForProcessingType(configType, query);

View File

@@ -2,10 +2,12 @@
// Licensed under the MIT license.
using Snap.Hutao.Model;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog;
@@ -13,65 +15,28 @@ namespace Snap.Hutao.Service.GachaLog;
/// <summary>
/// 祈愿记录服务上下文
/// </summary>
internal readonly struct GachaLogServiceMetadataContext
internal sealed class GachaLogServiceMetadataContext : IMetadataContext,
IMetadataSupportInitialization,
IMetadataListGachaEventSource,
IMetadataDictionaryIdAvatarSource,
IMetadataDictionaryIdWeaponSource,
IMetadataDictionaryNameAvatarSource,
IMetadataDictionaryNameWeaponSource
{
/// <summary>
/// 物品缓存
/// </summary>
public readonly Dictionary<string, Item> ItemCache = [];
public Dictionary<string, Item> ItemCache { get; set; } = [];
/// <summary>
/// Id 角色 映射
/// </summary>
public readonly Dictionary<AvatarId, Avatar> IdAvatarMap;
public List<GachaEvent> GachaEvents { get; set; } = default!;
/// <summary>
/// Id 武器 映射
/// </summary>
public readonly Dictionary<WeaponId, Weapon> IdWeaponMap;
public Dictionary<AvatarId, Avatar> IdAvatarMap { get; set; } = default!;
/// <summary>
/// 名称 角色 映射
/// </summary>
public readonly Dictionary<string, Avatar> NameAvatarMap;
public Dictionary<WeaponId, Weapon> IdWeaponMap { get; set; } = default!;
/// <summary>
/// 名称 武器 映射
/// </summary>
public readonly Dictionary<string, Weapon> NameWeaponMap;
public Dictionary<string, Avatar> NameAvatarMap { get; set; } = default!;
/// <summary>
/// 是否初始化完成
/// </summary>
public readonly bool IsInitialized;
public Dictionary<string, Weapon> NameWeaponMap { get; set; } = default!;
/// <summary>
/// 构造一个新的祈愿记录服务上下文
/// </summary>
/// <param name="idAvatarMap">Id 角色 映射</param>
/// <param name="idWeaponMap">Id 武器 映射</param>
/// <param name="nameAvatarMap">名称 角色 映射</param>
/// <param name="nameWeaponMap">名称 武器 映射</param>
public GachaLogServiceMetadataContext(
Dictionary<AvatarId, Avatar> idAvatarMap,
Dictionary<WeaponId, Weapon> idWeaponMap,
Dictionary<string, Avatar> nameAvatarMap,
Dictionary<string, Weapon> nameWeaponMap)
{
IdAvatarMap = idAvatarMap;
IdWeaponMap = idWeaponMap;
NameAvatarMap = nameAvatarMap;
NameWeaponMap = nameWeaponMap;
public bool IsInitialized { get; set; }
IsInitialized = true;
}
/// <summary>
/// 按名称获取物品
/// </summary>
/// <param name="name">名称</param>
/// <param name="type">类型</param>
/// <returns>物品</returns>
public Item GetItemByNameAndType(string name, string type)
{
if (!ItemCache.TryGetValue(name, out Item? result))
@@ -93,11 +58,6 @@ internal readonly struct GachaLogServiceMetadataContext
return result;
}
/// <summary>
/// 按物品 Id 获取名称星级
/// </summary>
/// <param name="id">Id</param>
/// <returns>名称星级</returns>
public INameQuality GetNameQualityByItemId(uint id)
{
uint place = id.StringLength();
@@ -109,12 +69,6 @@ internal readonly struct GachaLogServiceMetadataContext
};
}
/// <summary>
/// 获取物品 Id
/// O(1)
/// </summary>
/// <param name="item">祈愿物品</param>
/// <returns>物品 Id</returns>
public uint GetItemId(GachaLogItem item)
{
if (item.ItemType == SH.ModelInterchangeUIGFItemTypeAvatar)

View File

@@ -19,7 +19,7 @@ internal interface IGachaLogDbService
ValueTask RemoveGachaArchiveByIdAsync(Guid archiveId);
void RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaConfigType queryType, long endId);
void RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(Guid archiveId, GachaType queryType, long endId);
ValueTask<GachaArchive?> GetGachaArchiveByIdAsync(Guid archiveId, CancellationToken token);
@@ -31,25 +31,25 @@ internal interface IGachaLogDbService
ValueTask<List<GachaItem>> GetGachaItemListByArchiveIdAsync(Guid archiveId);
List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaConfigType queryType, long endId);
List<Web.Hutao.GachaLog.GachaItem> GetHutaoGachaItemList(Guid archiveId, GachaType queryType, long endId);
long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType);
long GetNewestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaType queryType);
ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token);
ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaType queryType, CancellationToken token);
long GetOldestGachaItemIdByArchiveId(Guid archiveId);
long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaConfigType queryType);
long GetOldestGachaItemIdByArchiveIdAndQueryType(Guid archiveId, GachaType queryType);
ValueTask<long> GetOldestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType, CancellationToken token);
ValueTask<long> GetOldestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaType queryType, CancellationToken token);
ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaConfigType queryType);
ValueTask<long> GetNewestGachaItemIdByArchiveIdAndQueryTypeAsync(Guid archiveId, GachaType queryType);
ValueTask<long> GetOldestGachaItemIdByArchiveIdAsync(Guid archiveId);
ValueTask<List<Web.Hutao.GachaLog.GachaItem>> GetHutaoGachaItemListAsync(Guid archiveId, GachaConfigType queryType, long endId);
ValueTask<List<Web.Hutao.GachaLog.GachaItem>> GetHutaoGachaItemListAsync(Guid archiveId, GachaType queryType, long endId);
ValueTask AddGachaItemRangeAsync(List<GachaItem> items);
ValueTask RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndIdAsync(Guid archiveId, GachaConfigType queryType, long endId);
ValueTask RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndIdAsync(Guid archiveId, GachaType queryType, long endId);
}

View File

@@ -36,7 +36,7 @@ internal interface IGachaLogHutaoCloudService
/// <param name="uid">uid</param>
/// <param name="token">取消令牌</param>
/// <returns>是否获取成功</returns>
ValueTask<ValueResult<bool, Guid>> RetrieveGachaItemsAsync(string uid, CancellationToken token = default);
ValueTask<ValueResult<bool, Guid>> RetrieveGachaArchiveIdAsync(string uid, CancellationToken token = default);
/// <summary>
/// 异步上传祈愿记录

View File

@@ -62,7 +62,7 @@ internal sealed partial class UIGFImportService : IUIGFImportService
Guid archiveId = archive.InnerId;
List<GachaItem> fullItems = [];
foreach (GachaConfigType queryType in GachaLog.QueryTypes)
foreach (GachaType queryType in GachaLog.QueryTypes)
{
long trimId = gachaLogDbService.GetOldestGachaItemIdByArchiveIdAndQueryType(archiveId, queryType);
logger.LogInformation("Last Id to trim with: [{Id}]", trimId);

View File

@@ -23,8 +23,15 @@ internal sealed class LaunchExecutionBetterGenshinImpactAutomationHandlder : ILa
Uri betterGenshinImpactUri = "bettergi://start".ToUri();
if (await Launcher.QueryUriSupportAsync(betterGenshinImpactUri, LaunchQuerySupportType.Uri) is LaunchQuerySupportStatus.Available)
{
context.Logger.LogInformation("Waiting game window to be ready");
context.Process.WaitForInputIdle();
try
{
context.Logger.LogInformation("Waiting game window to be ready");
context.Process.WaitForInputIdle();
}
catch (InvalidOperationException)
{
return;
}
context.Logger.LogInformation("Launching BetterGI");
await Launcher.LaunchUriAsync(betterGenshinImpactUri);

View File

@@ -168,9 +168,14 @@ internal sealed partial class PackageConverter
{
try
{
using (Stream remoteSteam = await httpClient.GetStreamAsync(pkgVersionUrl).ConfigureAwait(false))
// Server might close the connection shortly,
// we have to cache the content immediately.
using (HttpResponseMessage responseMessage = await httpClient.GetAsync(pkgVersionUrl, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false))
{
return await GetVersionItemsAsync(remoteSteam).ConfigureAwait(false);
using (Stream remoteSteam = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
return await GetVersionItemsAsync(remoteSteam).ConfigureAwait(false);
}
}
}
catch (IOException ex)

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryNameAvatarSource
{
public Dictionary<string, Model.Metadata.Avatar.Avatar> NameAvatarMap { get; set; }
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryNameWeaponSource
{
public Dictionary<string, Model.Metadata.Weapon.Weapon> NameWeaponMap { get; set; }
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata;
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataListGachaEventSource
{
public List<GachaEvent> GachaEvents { get; set; }
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataSupportInitialization
{
bool IsInitialized { get; set; }
}

View File

@@ -21,31 +21,51 @@ internal static class MetadataServiceContextExtension
{
listMaterialSource.Materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false);
}
if (context is IMetadataListGachaEventSource listGachaEventSource)
{
listGachaEventSource.GachaEvents = await metadataService.GetGachaEventListAsync(token).ConfigureAwait(false);
}
}
// Dictionary
{
if (context is IMetadataDictionaryIdAvatarSource dictionaryAvatarSource)
if (context is IMetadataDictionaryIdAvatarSource dictionaryIdAvatarSource)
{
dictionaryAvatarSource.IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
dictionaryIdAvatarSource.IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdMaterialSource dictionaryMaterialSource)
if (context is IMetadataDictionaryIdMaterialSource dictionaryIdMaterialSource)
{
dictionaryMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false);
dictionaryIdMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdWeaponSource dictionaryWeaponSource)
if (context is IMetadataDictionaryIdWeaponSource dictionaryIdWeaponSource)
{
dictionaryWeaponSource.IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
dictionaryIdWeaponSource.IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryNameAvatarSource dictionaryNameAvatarSource)
{
dictionaryNameAvatarSource.NameAvatarMap = await metadataService.GetNameToAvatarMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryNameWeaponSource dictionaryNameWeaponSource)
{
dictionaryNameWeaponSource.NameWeaponMap = await metadataService.GetNameToWeaponMapAsync(token).ConfigureAwait(false);
}
}
if (context is IMetadataSupportInitialization supportInitialization)
{
supportInitialization.IsInitialized = true;
}
return context;
}
#pragma warning disable SH002
public static IEnumerable<Material> EnumerateInventroyMaterial(this IMetadataListMaterialSource context)
public static IEnumerable<Material> EnumerateInventoryMaterial(this IMetadataListMaterialSource context)
{
return context.Materials.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id.Value);
}
@@ -55,6 +75,11 @@ internal static class MetadataServiceContextExtension
return context.IdAvatarMap[id];
}
public static Avatar GetAvatar(this IMetadataDictionaryNameAvatarSource context, string name)
{
return context.NameAvatarMap[name];
}
public static Material GetMaterial(this IMetadataDictionaryIdMaterialSource context, MaterialId id)
{
return context.IdMaterialMap[id];
@@ -64,5 +89,10 @@ internal static class MetadataServiceContextExtension
{
return context.IdWeaponMap[id];
}
public static Weapon GetWeapon(this IMetadataDictionaryNameWeaponSource context, string name)
{
return context.NameWeaponMap[name];
}
#pragma warning restore SH002
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Core.DependencyInjection.Abstraction;
namespace Snap.Hutao.Service.Metadata;

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.DependencyInjection.Abstraction;
namespace Snap.Hutao.Service.Navigation;

View File

@@ -3,7 +3,6 @@
using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Message;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;

View File

@@ -156,6 +156,7 @@
<None Remove="View\Control\LoadingView.xaml" />
<None Remove="View\Control\LoadingViewSlim.xaml" />
<None Remove="View\Control\SkillPivot.xaml" />
<None Remove="View\Control\SegmentedOverride.xaml" />
<None Remove="View\Control\StatisticsCard.xaml" />
<None Remove="View\Control\StatisticsSegmented.xaml" />
<None Remove="View\Control\WebViewer.xaml" />
@@ -298,6 +299,7 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
@@ -315,8 +317,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.15.3">
@@ -347,6 +349,11 @@
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<Page Update="Control\Theme\SegmentedOverride.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Control\Theme\Thickness.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -51,11 +51,12 @@
Grid.Row="0"
HorizontalAlignment="Right"
Text="{Binding DisplayName}"/>
<TextBlock
Grid.Row="1"
Margin="0,4,0,0"
Style="{StaticResource TitleTextBlockStyle}"
Text="{Binding FinishDescription}"/>
<Viewbox Grid.Row="1" StretchDirection="DownOnly">
<TextBlock
Margin="0,4,0,0"
Style="{StaticResource TitleTextBlockStyle}"
Text="{Binding FinishDescription}"/>
</Viewbox>
<ItemsControl
Grid.Row="2"
ItemTemplate="{StaticResource AchievementTempate}"

View File

@@ -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<string, string> 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<string, string> 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;

View File

@@ -22,35 +22,37 @@
</DataTemplate>
</UserControl.Resources>
<cwc:SettingsExpander
Header="{shcm:ResourceString Name=ViewControlBaseValueSliderLevel}"
IsExpanded="True"
ItemTemplate="{StaticResource BaseValueTemplate}"
ItemsSource="{x:Bind BaseValueInfo.Values, Mode=OneWay}">
<StackPanel
Height="16"
Margin="8,0,0,0"
Orientation="Horizontal"
Spacing="16">
<TextBlock VerticalAlignment="Center" Text="{x:Bind BaseValueInfo.CurrentLevelFormatted, Mode=OneWay}"/>
<CheckBox
MinWidth="0"
Margin="0,-8,0,-8"
Padding="8,0,0,0"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Content="{shcm:ResourceString Name=ViewControlBaseValueSliderPromoted}"
IsChecked="{x:Bind BaseValueInfo.Promoted, Mode=TwoWay}"
Visibility="{x:Bind IsPromoteVisible, Converter={StaticResource BoolToVisibilityConverter}}"/>
<Slider
MinWidth="240"
Margin="0,-16,0,-16"
VerticalAlignment="Center"
Maximum="{x:Bind BaseValueInfo.MaxLevel, Mode=OneWay}"
Minimum="1"
StepFrequency="1"
Value="{x:Bind BaseValueInfo.CurrentLevel, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsExpander>
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
<cwc:SettingsExpander
Header="{shcm:ResourceString Name=ViewControlBaseValueSliderLevel}"
IsExpanded="True"
ItemTemplate="{StaticResource BaseValueTemplate}"
ItemsSource="{x:Bind BaseValueInfo.Values, Mode=OneWay}">
<StackPanel
Height="16"
Margin="8,0,0,0"
Orientation="Horizontal"
Spacing="16">
<TextBlock VerticalAlignment="Center" Text="{x:Bind BaseValueInfo.CurrentLevelFormatted, Mode=OneWay}"/>
<CheckBox
MinWidth="0"
Margin="0,-8,0,-8"
Padding="8,0,0,0"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Content="{shcm:ResourceString Name=ViewControlBaseValueSliderPromoted}"
IsChecked="{x:Bind BaseValueInfo.Promoted, Mode=TwoWay}"
Visibility="{x:Bind IsPromoteVisible, Converter={StaticResource BoolToVisibilityConverter}}"/>
<Slider
MinWidth="240"
Margin="0,-16,0,-16"
VerticalAlignment="Center"
Maximum="{x:Bind BaseValueInfo.MaxLevel, Mode=OneWay}"
Minimum="1"
StepFrequency="1"
Value="{x:Bind BaseValueInfo.CurrentLevel, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsExpander>
</ScrollViewer>
</UserControl>

View File

@@ -26,17 +26,19 @@
</DataTemplate>
</UserControl.Resources>
<cwc:SettingsExpander
Header="{shcm:ResourceString Name=ViewControlBaseValueSliderLevel}"
IsExpanded="True"
ItemTemplate="{StaticResource ParameterDescriptionTemplate}"
ItemsSource="{x:Bind SelectedItem.Parameters, Mode=OneWay}">
<shc:SizeRestrictedContentControl Margin="0,-8">
<ComboBox
x:Name="LevelSelectorComboBox"
DisplayMemberPath="Level"
SelectionChanged="OnLevelSelectorComboBoxSelectionChanged"
Style="{StaticResource SettingsContentComboBoxStyle}"/>
</shc:SizeRestrictedContentControl>
</cwc:SettingsExpander>
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
<cwc:SettingsExpander
Header="{shcm:ResourceString Name=ViewControlBaseValueSliderLevel}"
IsExpanded="True"
ItemTemplate="{StaticResource ParameterDescriptionTemplate}"
ItemsSource="{x:Bind SelectedItem.Parameters, Mode=OneWay}">
<shc:SizeRestrictedContentControl Margin="0,-8">
<ComboBox
x:Name="LevelSelectorComboBox"
DisplayMemberPath="Level"
SelectionChanged="OnLevelSelectorComboBoxSelectionChanged"
Style="{StaticResource SettingsContentComboBoxStyle}"/>
</shc:SizeRestrictedContentControl>
</cwc:SettingsExpander>
</ScrollViewer>
</UserControl>

View File

@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
Style="{StaticResource DefaultSegmentedStyle}"
mc:Ignorable="d">
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewControlStatisticsSegmentedItemContentStatistics}" Icon="{shcm:FontIcon Glyph=&#xE9D2;}"/>
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewControlStatisticsSegmentedItemContentProportion}" Icon="{shcm:FontIcon Glyph=&#xEB05;}"/>

View File

@@ -243,6 +243,11 @@
PlaceholderText="{shcm:ResourceString Name=ViewPageAchievementSearchPlaceholder}"
QueryIcon="{shcm:FontIcon Glyph=&#xE721;}"
Text="{Binding SearchText, Mode=TwoWay}">
<AutoSuggestBox.TextBoxStyle>
<Style BasedOn="{StaticResource AutoSuggestBoxTextBoxStyle}" TargetType="TextBox">
<Setter Property="IsSpellCheckEnabled" Value="False"/>
</Style>
</AutoSuggestBox.TextBoxStyle>
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="QuerySubmitted">
<mxic:InvokeCommandAction Command="{Binding SearchAchievementCommand}" CommandParameter="{Binding SearchText}"/>

View File

@@ -484,7 +484,7 @@
</Grid.ColumnDefinitions>
<shvc:HutaoStatisticsCard Grid.Column="0" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent}"/>
<shvc:HutaoStatisticsCard Grid.Column="1" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent2}"/>
<shvc:HutaoStatisticsCard Grid.Column="2" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponWish}"/>
<shvc:HutaoStatisticsCard Grid.Column="2" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponEvent}"/>
</Grid>
</Grid>

View File

@@ -314,6 +314,17 @@
SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/>
</shc:SizeRestrictedContentControl>
</cwc:SettingsCard>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageSettingThemeDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingThemeHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE790;}">
<shc:SizeRestrictedContentControl>
<ComboBox
DisplayMemberPath="Name"
ItemsSource="{Binding AppOptions.LazyElementThemes.Value}"
SelectedItem="{Binding SelectedElementTheme, Mode=TwoWay}"/>
</shc:SizeRestrictedContentControl>
</cwc:SettingsCard>
<cwc:SettingsExpander
Description="{shcm:ResourceString Name=ViewPageSettingBackgroundImageDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingBackgroundImageHeader}"

View File

@@ -10,6 +10,7 @@
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shch="using:Snap.Hutao.Control.Helper"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shcp="using:Snap.Hutao.Control.Panel"
@@ -503,42 +504,58 @@
</mxi:Interaction.Behaviors>
<Grid.Resources>
<DataTemplate x:Key="AvatarViewTemplate" d:DataType="shvcom:AvatarView">
<shvcon:ItemIcon
Width="40"
Height="40"
shch:UIElementHelper.OpacityObject="{Binding Icon}"
Icon="{Binding Icon}"
Opacity="0"
Quality="{Binding Quality}"/>
</DataTemplate>
<DataTemplate x:Key="TeamItemTemplate" d:DataType="shvcom:Team">
<Border Margin="0,0,16,8" Style="{StaticResource BorderCardStyle}">
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="184"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl
MinWidth="210"
HorizontalAlignment="Left"
ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="6"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<shvcon:ItemIcon
Width="40"
Height="40"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Viewbox
Grid.Column="1"
Height="40"
Stretch="Uniform">
<Border Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Viewbox Stretch="Uniform">
<ItemsControl
Margin="4"
HorizontalAlignment="Left"
ItemTemplate="{StaticResource AvatarViewTemplate}"
ItemsPanel="{ThemeResource HorizontalStackPanelSpacing4Template}"
ItemsSource="{Binding}"/>
</Viewbox>
<Border
Grid.Row="1"
Background="{ThemeResource SolidBackgroundFillColorBaseBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0"
CornerRadius="{ThemeResource ControlCornerRadiusBottom}"
Opacity="0.7"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<TextBlock
Margin="4,8"
Grid.Column="0"
Margin="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Rank}"/>
<AppBarSeparator Grid.Column="1" Margin="-64,-2"/>
<TextBlock
Grid.Column="2"
Margin="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Rate}"/>
</Viewbox>
</Grid>
</Grid>
</Border>
</DataTemplate>
@@ -690,32 +707,59 @@
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView
Grid.Column="0"
Padding="0,0,0,0"
ItemContainerStyle="{ThemeResource NoneSelectionListViewItemStyle}"
ItemTemplate="{StaticResource TeamItemTemplate}"
ItemsSource="{Binding Up}"
SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<shcp:UniformPanel MinItemWidth="240"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<ListView
Grid.Column="1"
Padding="0,0,0,0"
ItemContainerStyle="{ThemeResource NoneSelectionListViewItemStyle}"
ItemTemplate="{StaticResource TeamItemTemplate}"
ItemsSource="{Binding Down}"
SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<shcp:UniformPanel MinItemWidth="240"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<cwc:HeaderedContentControl Grid.Column="0">
<cwc:HeaderedContentControl.Header>
<Border Margin="0,0,16,0" Style="{ThemeResource BorderCardStyle}">
<TextBlock
Margin="0,8"
HorizontalAlignment="Center"
Text="{shcm:ResourceString Name=ViewPageSpiralAbyssTeamAppearanceUpHeader}"/>
</Border>
</cwc:HeaderedContentControl.Header>
<ListView
Padding="0"
ItemContainerStyle="{ThemeResource NoneSelectionListViewItemStyle}"
ItemTemplate="{StaticResource TeamItemTemplate}"
ItemsSource="{Binding Up}"
SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<shcp:UniformPanel
Padding="0,0,16,0"
ColumnSpacing="6"
MinItemWidth="240"
RowSpacing="2"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</cwc:HeaderedContentControl>
<cwc:HeaderedContentControl Grid.Column="1">
<cwc:HeaderedContentControl.Header>
<Border Margin="0,0,16,0" Style="{ThemeResource BorderCardStyle}">
<TextBlock
Margin="0,8"
HorizontalAlignment="Center"
Text="{shcm:ResourceString Name=ViewPageSpiralAbyssTeamAppearanceDownHeader}"/>
</Border>
</cwc:HeaderedContentControl.Header>
<ListView
Padding="0"
ItemContainerStyle="{ThemeResource NoneSelectionListViewItemStyle}"
ItemTemplate="{StaticResource TeamItemTemplate}"
ItemsSource="{Binding Down}"
SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<shcp:UniformPanel
Padding="0,0,16,0"
ColumnSpacing="6"
MinItemWidth="240"
RowSpacing="2"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</cwc:HeaderedContentControl>
</Grid>
</Grid>
</Border>

View File

@@ -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 @@
</shct:DescriptionTextBlock>
</DataTemplate>
<DataTemplate x:Key="TokenTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle
Grid.Column="0"
Width="4"
Height="22"
Margin="0,0,8,0"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
RadiusX="2"
RadiusY="2"
Visibility="{Binding Quality, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Quality}"/>
</Rectangle.Fill>
</Rectangle>
<shci:MonoChrome
Grid.Column="0"
Width="22"
Height="22"
Margin="0,0,4,0"
Source="{Binding IconUri, Mode=OneWay}"
Visibility="{Binding IconUri, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
<shci:CachedImage
Grid.Column="0"
Width="22"
Height="22"
Margin="-4,-2,8,2"
Source="{Binding SideIconUri, Mode=OneWay}"
Visibility="{Binding SideIconUri, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding Value}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="AvatarListTemplate">
<Grid>
<Grid.ColumnDefinitions>
@@ -263,24 +304,33 @@
Margin="8,8,0,0"
LocalSettingKeySuffixForCurrent="WikiAvatarPage.Avatars"/>
</CommandBar.Content>
<!--<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE946;}" Label="搜索提示"/>-->
<AppBarElementContainer>
<AutoSuggestBox
Width="240"
Height="36"
Margin="6,6,6,0"
HorizontalAlignment="Stretch"
<shca:AutoSuggestTokenBox
Width="600"
Height="44"
Margin="6,-2,6,6"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
AvailableTokens="{Binding AvailableTokens}"
FilterCommand="{Binding FilterCommand}"
ItemsSource="{Binding FilterTokens, Mode=TwoWay}"
MaximumTokens="5"
PlaceholderText="{shcm:ResourceString Name=ViewPageWiKiAvatarAutoSuggestBoxPlaceHolder}"
QueryIcon="{shcm:FontIcon Glyph=&#xE721;}"
Style="{StaticResource DefaultAutoSuggestBoxStyle}"
Text="{Binding FilterText, Mode=TwoWay}">
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="QuerySubmitted">
<mxic:InvokeCommandAction Command="{Binding FilterCommand}" CommandParameter="{Binding FilterText}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</AutoSuggestBox>
QueryIcon="{cw:FontIconSource Glyph=&#xE721;}"
Style="{StaticResource DefaultTokenizingTextBoxStyle}"
SuggestedItemTemplate="{StaticResource TokenTemplate}"
SuggestedItemsSource="{Binding AvailableTokens.Values}"
Text="{Binding FilterToken, Mode=TwoWay}"
TokenItemTemplate="{StaticResource TokenTemplate}">
<shca:AutoSuggestTokenBox.ItemsPanel>
<ItemsPanelTemplate>
<cwc:WrapPanel
cw:FrameworkElementExtensions.AncestorType="shca:AutoSuggestTokenBox"
HorizontalSpacing="2"
StretchChild="Last"/>
</ItemsPanelTemplate>
</shca:AutoSuggestTokenBox.ItemsPanel>
</shca:AutoSuggestTokenBox>
</AppBarElementContainer>
<AppBarButton
Command="{Binding CultivateCommand}"

View File

@@ -9,6 +9,7 @@
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"
@@ -32,6 +33,47 @@
Source="{Binding Converter={StaticResource PropertyDescriptor}}"/>
</DataTemplate>
<DataTemplate x:Key="TokenTemplate">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle
Grid.Column="0"
Width="4"
Height="22"
Margin="0,0,8,0"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
RadiusX="2"
RadiusY="2"
Visibility="{Binding Quality, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Quality}"/>
</Rectangle.Fill>
</Rectangle>
<shci:MonoChrome
Grid.Column="0"
Width="22"
Height="22"
Margin="0,0,4,0"
Source="{Binding IconUri, Mode=OneWay}"
Visibility="{Binding IconUri, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
<shci:CachedImage
Grid.Column="0"
Width="22"
Height="22"
Margin="0,0,4,0"
Source="{Binding SideIconUri, Mode=OneWay}"
Visibility="{Binding SideIconUri, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding Value}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="WeaponListTemplate">
<Grid>
<Grid.ColumnDefinitions>
@@ -131,21 +173,32 @@
LocalSettingKeySuffixForCurrent="WikiWeaponPage.Weapons"/>
</CommandBar.Content>
<AppBarElementContainer>
<AutoSuggestBox
Width="240"
Height="36"
Margin="16,6,6,0"
<shca:AutoSuggestTokenBox
x:Name="WeaponSuggestBox"
Width="600"
Height="44"
Margin="6,-2,6,6"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center"
AvailableTokens="{Binding AvailableTokens}"
FilterCommand="{Binding FilterCommand}"
ItemsSource="{Binding FilterTokens, Mode=TwoWay}"
MaximumTokens="5"
PlaceholderText="{shcm:ResourceString Name=ViewPageWiKiWeaponAutoSuggestBoxPlaceHolder}"
QueryIcon="{shcm:FontIcon Glyph=&#xE721;}"
Text="{Binding FilterText, Mode=TwoWay}">
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="QuerySubmitted">
<mxic:InvokeCommandAction Command="{Binding FilterCommand}" CommandParameter="{Binding FilterText}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</AutoSuggestBox>
QueryIcon="{cw:FontIconSource Glyph=&#xE721;}"
SuggestedItemTemplate="{StaticResource TokenTemplate}"
SuggestedItemsSource="{Binding AvailableTokens.Values}"
Text="{Binding FilterToken, Mode=TwoWay}"
TokenItemTemplate="{StaticResource TokenTemplate}">
<shca:AutoSuggestTokenBox.ItemsPanel>
<ItemsPanelTemplate>
<cwc:WrapPanel
cw:FrameworkElementExtensions.AncestorType="shca:AutoSuggestTokenBox"
HorizontalSpacing="2"
StretchChild="Last"/>
</ItemsPanelTemplate>
</shca:AutoSuggestTokenBox.ItemsPanel>
</shca:AutoSuggestTokenBox>
</AppBarElementContainer>
<AppBarButton
Command="{Binding CultivateCommand}"

View File

@@ -16,27 +16,27 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
[HighQuality]
internal sealed class AvatarProperty : ObservableObject, INameIcon, IAlternatingItem
{
private static readonly FrozenDictionary<FightProperty, Uri> PropertyIcons = new Dictionary<FightProperty, Uri>()
{
[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<FightProperty, Uri> 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;

View File

@@ -20,7 +20,7 @@ internal sealed class Team : List<AvatarView>
/// </summary>
/// <param name="team">队伍</param>
/// <param name="idAvatarMap">映射</param>
public Team(ItemRate<string, int> team, Dictionary<AvatarId, Avatar> idAvatarMap)
public Team(ItemRate<string, int> team, Dictionary<AvatarId, Avatar> idAvatarMap, int rank)
: base(4)
{
foreach (StringSegment item in new StringTokenizer(team.Item, [',']))
@@ -29,11 +29,16 @@ internal sealed class Team : List<AvatarView>
Add(new(idAvatarMap[id]));
}
AddRange(new AvatarView[4 - Count]);
Rate = SH.FormatModelBindingHutaoTeamUpCountFormat(team.Rate);
Rank = rank;
}
/// <summary>
/// 上场次数
/// </summary>
public string Rate { get; }
public int Rank { get; set; }
}

View File

@@ -21,8 +21,8 @@ internal sealed class TeamAppearanceView
public TeamAppearanceView(TeamAppearance teamRank, Dictionary<AvatarId, Avatar> idAvatarMap)
{
Floor = SH.FormatModelBindingHutaoComplexRankFloor(teamRank.Floor);
Up = teamRank.Up.SelectList(teamRate => new Team(teamRate, idAvatarMap));
Down = teamRank.Down.SelectList(teamRate => new Team(teamRate, idAvatarMap));
Up = teamRank.Up.SelectList((teamRate, index) => new Team(teamRate, idAvatarMap, index + 1));
Down = teamRank.Down.SelectList((teamRate, index) => new Team(teamRate, idAvatarMap, index + 1));
}
/// <summary>

View File

@@ -19,6 +19,11 @@ internal sealed class GachaStatistics
/// </summary>
public TypedWishSummary WeaponWish { get; set; } = default!;
/// <summary>
/// 集录祈愿
/// </summary>
public TypedWishSummary ChronicledWish { get; set; } = default!;
/// <summary>
/// 奔行世间
/// </summary>

View File

@@ -60,7 +60,7 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
{
return await hutaoCloudService.RetrieveGachaItemsAsync(uid).ConfigureAwait(false);
return await hutaoCloudService.RetrieveGachaArchiveIdAsync(uid).ConfigureAwait(false);
}
}

View File

@@ -21,5 +21,10 @@ internal sealed class HutaoStatistics
/// <summary>
/// 神铸赋形
/// </summary>
public HutaoWishSummary WeaponWish { get; set; } = default!;
public HutaoWishSummary WeaponEvent { get; set; } = default!;
/// <summary>
/// 集录祈愿
/// </summary>
public HutaoWishSummary Chronicled { get; set; } = default!;
}

View File

@@ -21,7 +21,15 @@ internal abstract class Wish
public string TimeSpanFormatted
{
get => $"{From:yyyy.MM.dd} - {To:yyyy.MM.dd}";
get
{
if (From == DateTimeOffset.MaxValue && To == DateTimeOffset.MinValue)
{
return string.Empty;
}
return $"{From:yyyy.MM.dd} - {To:yyyy.MM.dd}";
}
}
/// <summary>

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
@@ -58,6 +59,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
private readonly IMessenger messenger;
private NameValue<BackdropType>? selectedBackdropType;
private NameValue<ElementTheme>? selectedElementTheme;
private NameValue<BackgroundImageType>? selectedBackgroundImageType;
private NameValue<CultureInfo>? selectedCulture;
private NameValue<Region>? selectedRegion;
@@ -94,6 +96,18 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
}
}
public NameValue<ElementTheme>? SelectedElementTheme
{
get => selectedElementTheme ??= AppOptions.LazyElementThemes.Value.Single(t => t.Value == AppOptions.ElementTheme);
set
{
if (SetProperty(ref selectedElementTheme, value) && value is not null)
{
AppOptions.ElementTheme = value.Value;
}
}
}
public NameValue<BackgroundImageType>? SelectedBackgroundImageType
{
get => selectedBackgroundImageType ??= AppOptions.BackgroundImageTypes.Single(t => t.Value == AppOptions.BackgroundImageType);

View File

@@ -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
/// </summary>
/// <param name="input">输入</param>
/// <returns>筛选操作</returns>
public static Predicate<Avatar> Compile(string input)
public static Predicate<Avatar> Compile(ObservableCollection<SearchToken> input)
{
return (Avatar avatar) => DoFilter(input, avatar);
}
private static bool DoFilter(string input, Avatar avatar)
private static bool DoFilter(ObservableCollection<SearchToken> input, Avatar avatar)
{
List<bool> matches = [];
foreach (StringSegment segment in new StringTokenizer(input, [' ']))
foreach ((SearchTokenKind kind, IEnumerable<string> 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);

View File

@@ -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
/// </summary>
/// <param name="input">输入</param>
/// <returns>筛选操作</returns>
public static Predicate<Weapon> Compile(string input)
public static Predicate<Weapon> Compile(ObservableCollection<SearchToken> input)
{
return (Weapon weapon) => DoFilter(input, weapon);
}
private static bool DoFilter(string input, Weapon weapon)
private static bool DoFilter(ObservableCollection<SearchToken> input, Weapon weapon)
{
List<bool> matches = [];
foreach (StringSegment segment in new StringTokenizer(input, [' ']))
foreach ((SearchTokenKind kind, IEnumerable<string> 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;
}
}

View File

@@ -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<Avatar>? avatars;
private Avatar? selected;
private string? filterText;
private ObservableCollection<SearchToken>? filterTokens;
private string? filterToken;
private BaseValueInfo? baseValueInfo;
private Dictionary<Level, Dictionary<GrowCurveType, float>>? levelAvatarCurveMap;
private List<Promote>? promotes;
private FrozenDictionary<string, SearchToken> availableTokens;
/// <summary>
/// 角色列表
@@ -75,9 +82,13 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); }
/// <summary>
/// 筛选文本
/// 保存的筛选标志
/// </summary>
public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); }
public ObservableCollection<SearchToken>? FilterTokens { get => filterTokens; set => SetProperty(ref filterTokens, value); }
public string? FilterToken { get => filterToken; set => SetProperty(ref filterToken, value); }
public FrozenDictionary<string, SearchToken>? AvailableTokens { get => availableTokens; }
protected override async ValueTask<bool> 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))
{

View File

@@ -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<Weapon>? weapons;
private Weapon? selected;
private string? filterText;
private ObservableCollection<SearchToken>? filterTokens;
private string? filterToken;
private BaseValueInfo? baseValueInfo;
private Dictionary<Level, Dictionary<GrowCurveType, float>>? levelWeaponCurveMap;
private List<Promote>? promotes;
private FrozenDictionary<string, SearchToken> availableTokens;
/// <summary>
/// 角色列表
@@ -72,9 +79,13 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); }
/// <summary>
/// 筛选文本
/// 保存的筛选标志
/// </summary>
public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); }
public ObservableCollection<SearchToken>? FilterTokens { get => filterTokens; set => SetProperty(ref filterTokens, value); }
public string? FilterToken { get => filterToken; set => SetProperty(ref filterToken, value); }
public FrozenDictionary<string, SearchToken>? AvailableTokens { get => availableTokens; }
/// <inheritdoc/>
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))
{

View File

@@ -10,11 +10,11 @@ internal static class GachaConfigTypeExtension
/// </summary>
/// <param name="configType">配置类型</param>
/// <returns>祈愿查询类型</returns>
public static GachaConfigType ToQueryType(this GachaConfigType configType)
public static GachaType ToQueryType(this GachaType configType)
{
return configType switch
{
GachaConfigType.AvatarEventWish2 => GachaConfigType.AvatarEventWish,
GachaType.SpecialActivityAvatar => GachaType.ActivityAvatar,
_ => configType,
};
}

Some files were not shown because too many files have changed in this diff Show More