diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs
new file mode 100644
index 00000000..48c1846c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/AttributeGenerator.cs
@@ -0,0 +1,147 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.CodeAnalysis;
+
+namespace Snap.Hutao.SourceGeneration.Automation;
+
+[Generator(LanguageNames.CSharp)]
+internal sealed class AttributeGenerator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ context.RegisterPostInitializationOutput(GenerateAllAttributes);
+ }
+
+ public static void GenerateAllAttributes(IncrementalGeneratorPostInitializationContext context)
+ {
+ string coreAnnotations = """
+ using System.Diagnostics;
+
+ namespace Snap.Hutao.Core.Annotation;
+
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ internal sealed class CommandAttribute : Attribute
+ {
+ public CommandAttribute(string name)
+ {
+ }
+
+ public bool AllowConcurrentExecutions { get; set; }
+ }
+
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ internal sealed class ConstructorGeneratedAttribute : Attribute
+ {
+ public ConstructorGeneratedAttribute()
+ {
+ }
+
+ public bool CallBaseConstructor { get; set; }
+ public bool ResolveHttpClient { get; set; }
+ }
+
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+ internal sealed class DependencyPropertyAttribute : Attribute
+ {
+ public DependencyPropertyAttribute(string name, Type type)
+ {
+ }
+
+ public DependencyPropertyAttribute(string name, Type type, object defaultValue)
+ {
+ }
+
+ public DependencyPropertyAttribute(string name, Type type, object defaultValue, string valueChangedCallbackName)
+ {
+ }
+
+ public bool IsAttached { get; set; }
+ public Type AttachedType { get; set; } = default;
+ }
+
+ [AttributeUsage(AttributeTargets.All, Inherited = false)]
+ [Conditional("DEBUG")]
+ internal sealed class HighQualityAttribute : Attribute
+ {
+ }
+ """;
+ context.AddSource("Snap.Hutao.Core.Annotation.Attributes.g.cs", coreAnnotations);
+
+ string coreDependencyInjectionAnnotationHttpClients = """
+ namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
+
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ internal sealed class HttpClientAttribute : Attribute
+ {
+ public HttpClientAttribute(HttpClientConfiguration configuration)
+ {
+ }
+
+ public HttpClientAttribute(HttpClientConfiguration configuration, Type interfaceType)
+ {
+ }
+ }
+
+ internal enum HttpClientConfiguration
+ {
+ ///
+ /// 默认配置
+ ///
+ Default,
+
+ ///
+ /// 米游社请求配置
+ ///
+ XRpc,
+
+ ///
+ /// 米游社登录请求配置
+ ///
+ XRpc2,
+
+ ///
+ /// Hoyolab app
+ ///
+ XRpc3,
+ }
+
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ internal sealed class PrimaryHttpMessageHandlerAttribute : Attribute
+ {
+ ///
+ public int MaxConnectionsPerServer { get; set; }
+
+ ///
+ ///
+ ///
+ public bool UseCookies { get; set; }
+ }
+ """;
+ context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.Attributes.g.cs", coreDependencyInjectionAnnotationHttpClients);
+
+ string coreDependencyInjectionAnnotations = """
+ namespace Snap.Hutao.Core.DependencyInjection.Annotation;
+
+ internal enum InjectAs
+ {
+ Singleton,
+ Transient,
+ Scoped,
+ }
+
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+ internal sealed class InjectionAttribute : Attribute
+ {
+ public InjectionAttribute(InjectAs injectAs)
+ {
+ }
+
+ public InjectionAttribute(InjectAs injectAs, Type interfaceType)
+ {
+ }
+ }
+ """;
+ context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.Attributes.g.cs", coreDependencyInjectionAnnotations);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/CommandGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/CommandGenerator.cs
index 3bc345e8..83b1d206 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/CommandGenerator.cs
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/CommandGenerator.cs
@@ -95,4 +95,4 @@ internal sealed class CommandGenerator : IIncrementalGenerator
string normalizedClassName = classSymbol.ToDisplayString().Replace('<', '{').Replace('>', '}');
production.AddSource($"{normalizedClassName}.{commandName}.g.cs", code);
}
-}
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/SaltConstantGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/SaltConstantGenerator.cs
new file mode 100644
index 00000000..862d1101
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Automation/SaltConstantGenerator.cs
@@ -0,0 +1,61 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using System.Net.Http;
+using System.Runtime.Serialization;
+
+namespace Snap.Hutao.SourceGeneration.Automation;
+
+[Generator(LanguageNames.CSharp)]
+internal sealed class SaltConstantGenerator : IIncrementalGenerator
+{
+ private static readonly HttpClient httpClient = new();
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ context.RegisterPostInitializationOutput(GenerateSaltContstants);
+ }
+
+ private static void GenerateSaltContstants(IncrementalGeneratorPostInitializationContext context)
+ {
+ string body = httpClient.GetStringAsync("https://internal.snapgenshin.cn/Archive/Salt/Latest").GetAwaiter().GetResult();
+ Response saltInfo = JsonParser.FromJson>(body)!;
+ string code = $$"""
+ namespace Snap.Hutao.Web.Hoyolab;
+
+ internal sealed class SaltConstants
+ {
+ public const string CNVersion = "{{saltInfo.Data.CNVersion}}";
+ public const string CNK2 = "{{saltInfo.Data.CNK2}}";
+ public const string CNLK2 = "{{saltInfo.Data.CNLK2}}";
+
+ public const string OSVersion = "{{saltInfo.Data.OSVersion}}";
+ public const string OSK2 = "{{saltInfo.Data.OSK2}}";
+ public const string OSLK2 = "{{saltInfo.Data.OSLK2}}";
+ }
+ """;
+ context.AddSource("SaltConstants.g.cs", code);
+ }
+
+ private sealed class Response
+ {
+ [DataMember(Name = "data")]
+ public T Data { get; set; } = default!;
+ }
+
+ internal sealed class SaltLatest
+ {
+ public string CNVersion { get; set; } = default!;
+
+ public string CNK2 { get; set; } = default!;
+
+ public string CNLK2 { get; set; } = default!;
+
+ public string OSVersion { get; set; } = default!;
+
+ public string OSK2 { get; set; } = default!;
+
+ public string OSLK2 { get; set; } = default!;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/CodeAnalysis/NotNullWhenAttribute.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/CodeAnalysis/NotNullWhenAttribute.cs
index 10e8a5e3..3a2eb152 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/CodeAnalysis/NotNullWhenAttribute.cs
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/CodeAnalysis/NotNullWhenAttribute.cs
@@ -8,7 +8,10 @@ internal sealed class NotNullWhenAttribute : Attribute
///
/// The return value condition. If the method returns this value, the associated parameter will not be null.
///
- public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+ public NotNullWhenAttribute(bool returnValue)
+ {
+ ReturnValue = returnValue;
+ }
/// Gets the return value condition.
public bool ReturnValue { get; }
diff --git a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Identity/IdentityGenerator.cs b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Identity/IdentityGenerator.cs
index 26de90b7..a3440e94 100644
--- a/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Identity/IdentityGenerator.cs
+++ b/src/Snap.Hutao/Snap.Hutao.SourceGeneration/Identity/IdentityGenerator.cs
@@ -89,6 +89,12 @@ internal sealed class IdentityGenerator : IIncrementalGenerator
{
return Value.GetHashCode();
}
+
+ ///
+ public override string ToString()
+ {
+ return Value.ToString();
+ }
}
""");
diff --git a/src/Snap.Hutao/Snap.Hutao.Test/JsonSerializeTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/JsonSerializeTest.cs
index eb6473bb..98b818a1 100644
--- a/src/Snap.Hutao/Snap.Hutao.Test/JsonSerializeTest.cs
+++ b/src/Snap.Hutao/Snap.Hutao.Test/JsonSerializeTest.cs
@@ -49,7 +49,7 @@ public class JsonSerializeTest
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
- Dictionary sample = JsonSerializer.Deserialize>(SmapleNumberKeyDictionaryJson, options)!;
+ Dictionary sample = JsonSerializer.Deserialize>(SmapleNumberKeyDictionaryJson, options)!;
Assert.AreEqual(sample[111], "12");
}
diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml
index 40bb3356..a4d22ee8 100644
--- a/src/Snap.Hutao/Snap.Hutao/App.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/App.xaml
@@ -2,8 +2,8 @@
x:Class="Snap.Hutao.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:cwc="using:CommunityToolkit.WinUI.Converters"
xmlns:cwcw="using:CommunityToolkit.WinUI.Controls"
- xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
@@ -13,6 +13,7 @@
+
@@ -117,9 +118,9 @@
-
-
-
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnLoadedBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnLoadedBehavior.cs
index 6d610604..0bfe05bc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnLoadedBehavior.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnLoadedBehavior.cs
@@ -14,12 +14,36 @@ namespace Snap.Hutao.Control.Behavior;
[DependencyProperty("CommandParameter", typeof(object))]
internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase
{
+ private bool executed;
+
+ protected override void OnAttached()
+ {
+ base.OnAttached();
+
+ // FrameworkElement in a ItemsRepeater gets attached twice
+ if (AssociatedObject is FrameworkElement { IsLoaded: true })
+ {
+ TryExecuteCommand();
+ }
+ }
+
///
protected override void OnAssociatedObjectLoaded()
{
+ TryExecuteCommand();
+ }
+
+ private void TryExecuteCommand()
+ {
+ if (executed)
+ {
+ return;
+ }
+
if (Command is not null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
+ executed = true;
}
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Helper/FrameworkElementHelper.cs b/src/Snap.Hutao/Snap.Hutao/Control/Helper/FrameworkElementHelper.cs
index 9b001414..45bc1950 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Helper/FrameworkElementHelper.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Helper/FrameworkElementHelper.cs
@@ -11,8 +11,8 @@ public sealed partial class FrameworkElementHelper
{
private static void OnSquareLengthChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
- Microsoft.UI.Xaml.Controls.Control control = (Microsoft.UI.Xaml.Controls.Control)dp;
- control.Width = (double)e.NewValue;
- control.Height = (double)e.NewValue;
+ FrameworkElement element = (FrameworkElement)dp;
+ element.Width = (double)e.NewValue;
+ element.Height = (double)e.NewValue;
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Helper/InfoBarHelper.cs b/src/Snap.Hutao/Snap.Hutao/Control/Helper/InfoBarHelper.cs
new file mode 100644
index 00000000..8f69a804
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Helper/InfoBarHelper.cs
@@ -0,0 +1,13 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Snap.Hutao.Control.Helper;
+
+[SuppressMessage("", "SH001")]
+[DependencyProperty("IsTextSelectionEnabled", typeof(bool), false, IsAttached = true, AttachedType = typeof(InfoBar))]
+public sealed partial class InfoBarHelper
+{
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs
index f89e1ffc..e05dc521 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs
@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Core.Caching;
@@ -13,7 +12,7 @@ namespace Snap.Hutao.Control.Image;
/// 缓存图像
///
[HighQuality]
-internal sealed class CachedImage : ImageEx
+internal sealed class CachedImage : Implementation.ImageEx
{
///
/// 构造一个新的缓存图像
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageEx.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageEx.cs
new file mode 100644
index 00000000..18121e3e
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageEx.cs
@@ -0,0 +1,44 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml;
+using Windows.Media.Casting;
+
+namespace Snap.Hutao.Control.Image.Implementation;
+
+internal class ImageEx : ImageExBase
+{
+ private static readonly DependencyProperty NineGridProperty = DependencyProperty.Register(nameof(NineGrid), typeof(Thickness), typeof(ImageEx), new PropertyMetadata(default(Thickness)));
+
+ public ImageEx()
+ : base()
+ {
+ }
+
+ public Thickness NineGrid
+ {
+ get => (Thickness)GetValue(NineGridProperty);
+ set => SetValue(NineGridProperty, value);
+ }
+
+ public override CompositionBrush GetAlphaMask()
+ {
+ if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)
+ {
+ return image.GetAlphaMask();
+ }
+
+ return default!;
+ }
+
+ public CastingSource GetAsCastingSource()
+ {
+ if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)
+ {
+ return image.GetAsCastingSource();
+ }
+
+ return default!;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExBase.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExBase.cs
new file mode 100644
index 00000000..63ab013a
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExBase.cs
@@ -0,0 +1,468 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.WinUI;
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Media.Imaging;
+using System.IO;
+using Windows.Foundation;
+
+namespace Snap.Hutao.Control.Image.Implementation;
+
+internal delegate void ImageExFailedEventHandler(object sender, ImageExFailedEventArgs e);
+
+internal delegate void ImageExOpenedEventHandler(object sender, ImageExOpenedEventArgs e);
+
+[SuppressMessage("", "CA1001")]
+[SuppressMessage("", "SH003")]
+[TemplateVisualState(Name = LoadingState, GroupName = CommonGroup)]
+[TemplateVisualState(Name = LoadedState, GroupName = CommonGroup)]
+[TemplateVisualState(Name = UnloadedState, GroupName = CommonGroup)]
+[TemplateVisualState(Name = FailedState, GroupName = CommonGroup)]
+[TemplatePart(Name = PartImage, Type = typeof(object))]
+internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlphaMaskProvider
+{
+ protected const string PartImage = "Image";
+ protected const string CommonGroup = "CommonStates";
+ protected const string LoadingState = "Loading";
+ protected const string LoadedState = "Loaded";
+ protected const string UnloadedState = "Unloaded";
+ protected const string FailedState = "Failed";
+
+ private static readonly DependencyProperty StretchProperty = DependencyProperty.Register(nameof(Stretch), typeof(Stretch), typeof(ImageExBase), new PropertyMetadata(Stretch.Uniform));
+ private static readonly DependencyProperty DecodePixelHeightProperty = DependencyProperty.Register(nameof(DecodePixelHeight), typeof(int), typeof(ImageExBase), new PropertyMetadata(0));
+ private static readonly DependencyProperty DecodePixelTypeProperty = DependencyProperty.Register(nameof(DecodePixelType), typeof(int), typeof(ImageExBase), new PropertyMetadata(DecodePixelType.Physical));
+ private static readonly DependencyProperty DecodePixelWidthProperty = DependencyProperty.Register(nameof(DecodePixelWidth), typeof(int), typeof(ImageExBase), new PropertyMetadata(0));
+ private static readonly DependencyProperty IsCacheEnabledProperty = DependencyProperty.Register(nameof(IsCacheEnabled), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false));
+ private static readonly DependencyProperty EnableLazyLoadingProperty = DependencyProperty.Register(nameof(EnableLazyLoading), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false, EnableLazyLoadingChanged));
+ private static readonly DependencyProperty LazyLoadingThresholdProperty = DependencyProperty.Register(nameof(LazyLoadingThreshold), typeof(double), typeof(ImageExBase), new PropertyMetadata(default(double), LazyLoadingThresholdChanged));
+ private static readonly DependencyProperty PlaceholderSourceProperty = DependencyProperty.Register(nameof(PlaceholderSource), typeof(ImageSource), typeof(ImageExBase), new PropertyMetadata(default(ImageSource), PlaceholderSourceChanged));
+ private static readonly DependencyProperty PlaceholderStretchProperty = DependencyProperty.Register(nameof(PlaceholderStretch), typeof(Stretch), typeof(ImageExBase), new PropertyMetadata(default(Stretch)));
+ private static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(object), typeof(ImageExBase), new PropertyMetadata(null, SourceChanged));
+
+ private CancellationTokenSource? tokenSource;
+ private object? lazyLoadingSource;
+ private bool isInViewport;
+
+ public event ImageExFailedEventHandler? ImageExFailed;
+
+ public event ImageExOpenedEventHandler? ImageExOpened;
+
+ public event EventHandler? ImageExInitialized;
+
+ public bool IsInitialized { get; private set; }
+
+ public int DecodePixelHeight
+ {
+ get => (int)GetValue(DecodePixelHeightProperty);
+ set => SetValue(DecodePixelHeightProperty, value);
+ }
+
+ public DecodePixelType DecodePixelType
+ {
+ get => (DecodePixelType)GetValue(DecodePixelTypeProperty);
+ set => SetValue(DecodePixelTypeProperty, value);
+ }
+
+ public int DecodePixelWidth
+ {
+ get => (int)GetValue(DecodePixelWidthProperty);
+ set => SetValue(DecodePixelWidthProperty, value);
+ }
+
+ public Stretch Stretch
+ {
+ get => (Stretch)GetValue(StretchProperty);
+ set => SetValue(StretchProperty, value);
+ }
+
+ public bool IsCacheEnabled
+ {
+ get => (bool)GetValue(IsCacheEnabledProperty);
+ set => SetValue(IsCacheEnabledProperty, value);
+ }
+
+ public bool EnableLazyLoading
+ {
+ get => (bool)GetValue(EnableLazyLoadingProperty);
+ set => SetValue(EnableLazyLoadingProperty, value);
+ }
+
+ public double LazyLoadingThreshold
+ {
+ get => (double)GetValue(LazyLoadingThresholdProperty);
+ set => SetValue(LazyLoadingThresholdProperty, value);
+ }
+
+ public ImageSource PlaceholderSource
+ {
+ get => (ImageSource)GetValue(PlaceholderSourceProperty);
+ set => SetValue(PlaceholderSourceProperty, value);
+ }
+
+ public Stretch PlaceholderStretch
+ {
+ get => (Stretch)GetValue(PlaceholderStretchProperty);
+ set => SetValue(PlaceholderStretchProperty, value);
+ }
+
+ public object Source
+ {
+ get => GetValue(SourceProperty);
+ set => SetValue(SourceProperty, value);
+ }
+
+ public bool WaitUntilLoaded
+ {
+ get => true;
+ }
+
+ protected object? Image { get; private set; }
+
+ public abstract CompositionBrush GetAlphaMask();
+
+ protected virtual void OnPlaceholderSourceChanged(DependencyPropertyChangedEventArgs e)
+ {
+ }
+
+ protected virtual Task ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
+ {
+ // By default we just use the built-in UWP image cache provided within the Image control.
+ return Task.FromResult(new BitmapImage(imageUri));
+ }
+
+ protected virtual void OnImageOpened(object sender, RoutedEventArgs e)
+ {
+ VisualStateManager.GoToState(this, LoadedState, true);
+ ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
+ }
+
+ protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs e)
+ {
+ VisualStateManager.GoToState(this, FailedState, true);
+ ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new FileNotFoundException(e.ErrorMessage)));
+ }
+
+ protected void AttachImageOpened(RoutedEventHandler handler)
+ {
+ if (Image is Microsoft.UI.Xaml.Controls.Image image)
+ {
+ image.ImageOpened += handler;
+ }
+ else if (Image is ImageBrush brush)
+ {
+ brush.ImageOpened += handler;
+ }
+ }
+
+ protected void RemoveImageOpened(RoutedEventHandler handler)
+ {
+ if (Image is Microsoft.UI.Xaml.Controls.Image image)
+ {
+ image.ImageOpened -= handler;
+ }
+ else if (Image is ImageBrush brush)
+ {
+ brush.ImageOpened -= handler;
+ }
+ }
+
+ protected void AttachImageFailed(ExceptionRoutedEventHandler handler)
+ {
+ if (Image is Microsoft.UI.Xaml.Controls.Image image)
+ {
+ image.ImageFailed += handler;
+ }
+ else if (Image is ImageBrush brush)
+ {
+ brush.ImageFailed += handler;
+ }
+ }
+
+ protected void RemoveImageFailed(ExceptionRoutedEventHandler handler)
+ {
+ if (Image is Microsoft.UI.Xaml.Controls.Image image)
+ {
+ image.ImageFailed -= handler;
+ }
+ else if (Image is ImageBrush brush)
+ {
+ brush.ImageFailed -= handler;
+ }
+ }
+
+ protected override void OnApplyTemplate()
+ {
+ RemoveImageOpened(OnImageOpened);
+ RemoveImageFailed(OnImageFailed);
+
+ Image = GetTemplateChild(PartImage);
+
+ IsInitialized = true;
+
+ ImageExInitialized?.Invoke(this, EventArgs.Empty);
+
+ if (Source is null || !EnableLazyLoading || isInViewport)
+ {
+ lazyLoadingSource = null;
+ SetSource(Source);
+ }
+ else
+ {
+ lazyLoadingSource = Source;
+ }
+
+ AttachImageOpened(OnImageOpened);
+ AttachImageFailed(OnImageFailed);
+
+ base.OnApplyTemplate();
+ }
+
+ private static void EnableLazyLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is ImageExBase control)
+ {
+ bool value = (bool)e.NewValue;
+ if (value)
+ {
+ control.LayoutUpdated += control.ImageExBase_LayoutUpdated;
+
+ control.InvalidateLazyLoading();
+ }
+ else
+ {
+ control.LayoutUpdated -= control.ImageExBase_LayoutUpdated;
+ }
+ }
+ }
+
+ private static void LazyLoadingThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is ImageExBase control && control.EnableLazyLoading)
+ {
+ control.InvalidateLazyLoading();
+ }
+ }
+
+ private static void PlaceholderSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is ImageExBase control)
+ {
+ control.OnPlaceholderSourceChanged(e);
+ }
+ }
+
+ private static void SourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is not ImageExBase control)
+ {
+ return;
+ }
+
+ if (e.OldValue is null || e.NewValue is null || !e.OldValue.Equals(e.NewValue))
+ {
+ if (e.NewValue is null || !control.EnableLazyLoading || control.isInViewport)
+ {
+ control.lazyLoadingSource = null;
+ control.SetSource(e.NewValue);
+ }
+ else
+ {
+ control.lazyLoadingSource = e.NewValue;
+ }
+ }
+ }
+
+ private static bool IsHttpUri(Uri uri)
+ {
+ return uri.IsAbsoluteUri && (uri.Scheme == "http" || uri.Scheme == "https");
+ }
+
+ private void AttachSource(ImageSource? source)
+ {
+ // Setting the source at this point should call ImageExOpened/VisualStateManager.GoToState
+ // as we register to both the ImageOpened/ImageFailed events of the underlying control.
+ // We only need to call those methods if we fail in other cases before we get here.
+ if (Image is Microsoft.UI.Xaml.Controls.Image image)
+ {
+ image.Source = source;
+ }
+ else if (Image is ImageBrush brush)
+ {
+ brush.ImageSource = source;
+ }
+
+ if (source is null)
+ {
+ VisualStateManager.GoToState(this, UnloadedState, true);
+ }
+ else if (source is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 })
+ {
+ VisualStateManager.GoToState(this, LoadedState, true);
+ ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
+ }
+ }
+
+ [SuppressMessage("", "IDE0019")]
+ private async void SetSource(object? source)
+ {
+ if (!IsInitialized)
+ {
+ return;
+ }
+
+ tokenSource?.Cancel();
+
+ tokenSource = new CancellationTokenSource();
+
+ AttachSource(null);
+
+ if (source is null)
+ {
+ return;
+ }
+
+ VisualStateManager.GoToState(this, LoadingState, true);
+
+ ImageSource? imageSource = source as ImageSource;
+ if (imageSource is not null)
+ {
+ AttachSource(imageSource);
+
+ return;
+ }
+
+ Uri? uri = source as Uri;
+ if (uri is null)
+ {
+ string? url = source as string ?? source.ToString();
+ if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri))
+ {
+ VisualStateManager.GoToState(this, FailedState, true);
+ ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new UriFormatException("Invalid uri specified.")));
+ return;
+ }
+ }
+
+ if (!IsHttpUri(uri) && !uri.IsAbsoluteUri)
+ {
+ uri = new Uri("ms-appx:///" + uri.OriginalString.TrimStart('/'));
+ }
+
+ try
+ {
+ await LoadImageAsync(uri, tokenSource.Token).ConfigureAwait(true);
+ }
+ catch (OperationCanceledException)
+ {
+ // nothing to do as cancellation has been requested.
+ }
+ catch (Exception e)
+ {
+ VisualStateManager.GoToState(this, FailedState, true);
+ ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
+ }
+ }
+
+ private async Task LoadImageAsync(Uri imageUri, CancellationToken token)
+ {
+ if (imageUri is not null)
+ {
+ if (IsCacheEnabled)
+ {
+ ImageSource? img = await ProvideCachedResourceAsync(imageUri, token).ConfigureAwait(true);
+
+ ArgumentNullException.ThrowIfNull(tokenSource);
+ if (!tokenSource.IsCancellationRequested)
+ {
+ // Only attach our image if we still have a valid request.
+ AttachSource(img);
+ }
+ }
+ else if (string.Equals(imageUri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
+ {
+ string source = imageUri.OriginalString;
+ const string base64Head = "base64,";
+ int index = source.IndexOf(base64Head, StringComparison.Ordinal);
+ if (index >= 0)
+ {
+ byte[] bytes = Convert.FromBase64String(source[(index + base64Head.Length)..]);
+ BitmapImage bitmap = new();
+ await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream());
+
+ ArgumentNullException.ThrowIfNull(tokenSource);
+ if (!tokenSource.IsCancellationRequested)
+ {
+ AttachSource(bitmap);
+ }
+ }
+ }
+ else
+ {
+ AttachSource(new BitmapImage(imageUri)
+ {
+ CreateOptions = BitmapCreateOptions.IgnoreImageCache,
+ });
+ }
+ }
+ }
+
+ private void ImageExBase_LayoutUpdated(object? sender, object e)
+ {
+ InvalidateLazyLoading();
+ }
+
+ private void InvalidateLazyLoading()
+ {
+ if (!IsLoaded)
+ {
+ isInViewport = false;
+ return;
+ }
+
+ // Find the first ascendant ScrollViewer, if not found, use the root element.
+ FrameworkElement? hostElement = default;
+ IEnumerable ascendants = this.FindAscendants().OfType();
+ foreach (FrameworkElement ascendant in ascendants)
+ {
+ hostElement = ascendant;
+ if (hostElement is Microsoft.UI.Xaml.Controls.ScrollViewer)
+ {
+ break;
+ }
+ }
+
+ if (hostElement is null)
+ {
+ isInViewport = false;
+ return;
+ }
+
+ Rect controlRect = TransformToVisual(hostElement)
+ .TransformBounds(new Rect(0, 0, ActualWidth, ActualHeight));
+ double lazyLoadingThreshold = LazyLoadingThreshold;
+ Rect hostRect = new(
+ 0 - lazyLoadingThreshold,
+ 0 - lazyLoadingThreshold,
+ hostElement.ActualWidth + (2 * lazyLoadingThreshold),
+ hostElement.ActualHeight + (2 * lazyLoadingThreshold));
+
+ if (controlRect.IntersectsWith(hostRect))
+ {
+ isInViewport = true;
+
+ if (lazyLoadingSource is not null)
+ {
+ object source = lazyLoadingSource;
+ lazyLoadingSource = null;
+ SetSource(source);
+ }
+ }
+ else
+ {
+ isInViewport = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExFailedEventArgs.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExFailedEventArgs.cs
new file mode 100644
index 00000000..b6841b6c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExFailedEventArgs.cs
@@ -0,0 +1,17 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Control.Image.Implementation;
+
+internal sealed class ImageExFailedEventArgs : EventArgs
+{
+ public ImageExFailedEventArgs(Exception errorException)
+ {
+ ErrorMessage = ErrorException?.Message;
+ ErrorException = errorException;
+ }
+
+ public Exception? ErrorException { get; private set; }
+
+ public string? ErrorMessage { get; private set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExOpenedEventArgs.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExOpenedEventArgs.cs
new file mode 100644
index 00000000..b6926fed
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/Implementation/ImageExOpenedEventArgs.cs
@@ -0,0 +1,8 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Control.Image.Implementation;
+
+internal sealed class ImageExOpenedEventArgs : EventArgs
+{
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs b/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs
new file mode 100644
index 00000000..a2cdec3f
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Loading.cs
@@ -0,0 +1,48 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml;
+
+namespace Snap.Hutao.Control;
+
+[TemplateVisualState(Name = "LoadingIn", GroupName = "CommonStates")]
+[TemplateVisualState(Name = "LoadingOut", GroupName = "CommonStates")]
+internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
+{
+ public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged));
+
+ [SuppressMessage("", "IDE0052")]
+ private FrameworkElement? presenter;
+
+ public Loading()
+ {
+ DefaultStyleKey = typeof(Loading);
+ DefaultStyleResourceUri = new("ms-appx:///Control/Loading.xaml");
+ }
+
+ public bool IsLoading
+ {
+ get => (bool)GetValue(IsLoadingProperty);
+ set => SetValue(IsLoadingProperty, value);
+ }
+
+ protected override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ Update();
+ }
+
+ private static void IsLoadingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ Loading control = (Loading)d;
+ control.presenter ??= control.GetTemplateChild("ContentGrid") as FrameworkElement;
+
+ control?.Update();
+ }
+
+ private void Update()
+ {
+ VisualStateManager.GoToState(this, IsLoading ? "LoadingIn" : "LoadingOut", true);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Loading.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Loading.xaml
new file mode 100644
index 00000000..7d5e4fc5
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Loading.xaml
@@ -0,0 +1,85 @@
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Media/Bgra32.cs b/src/Snap.Hutao/Snap.Hutao/Control/Media/Bgra32.cs
index 50d2da48..eda7c14c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Media/Bgra32.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Media/Bgra32.cs
@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Microsoft.UI.Xaml.Media;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using Windows.UI;
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba32.cs b/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba32.cs
index ea714a86..836571c3 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba32.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba32.cs
@@ -39,7 +39,7 @@ internal struct Rgba32
///
/// 色值字符串
public Rgba32(string hex)
- : this(Convert.ToUInt32(hex, 16))
+ : this(hex.Length == 6 ? Convert.ToUInt32($"{hex}FF", 16) : Convert.ToUInt32(hex, 16))
{
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs
index 9c596a85..ecbdbaf5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs
@@ -20,6 +20,7 @@ namespace Snap.Hutao.Control.Text;
///
[HighQuality]
[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
+[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
internal sealed partial class DescriptionTextBlock : ContentControl
{
private static readonly int ColorTagFullLength = "".Length;
@@ -40,6 +41,7 @@ internal sealed partial class DescriptionTextBlock : ContentControl
Content = new TextBlock()
{
TextWrapping = TextWrapping.Wrap,
+ Style = TextStyle,
};
actualThemeChangedEventHandler = OnActualThemeChanged;
@@ -54,6 +56,12 @@ internal sealed partial class DescriptionTextBlock : ContentControl
UpdateDescription(textBlock, description);
}
+ private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
+ textBlock.Style = (Style)e.NewValue;
+ }
+
private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan description)
{
textBlock.Inlines.Clear();
@@ -66,7 +74,7 @@ internal sealed partial class DescriptionTextBlock : ContentControl
{
AppendText(textBlock, description[last..i]);
AppendLineBreak(textBlock);
- i += 1;
+ i += 2;
last = i;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Text/HtmlDescriptionTextBlock.cs b/src/Snap.Hutao/Snap.Hutao/Control/Text/HtmlDescriptionTextBlock.cs
new file mode 100644
index 00000000..12ad53ad
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Text/HtmlDescriptionTextBlock.cs
@@ -0,0 +1,182 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Text;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Documents;
+using Microsoft.UI.Xaml.Media;
+using Snap.Hutao.Control.Extension;
+using Snap.Hutao.Control.Media;
+using Snap.Hutao.Control.Theme;
+using Windows.Foundation;
+using Windows.UI;
+
+namespace Snap.Hutao.Control.Text;
+
+[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
+[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
+internal sealed partial class HtmlDescriptionTextBlock : ContentControl
+{
+ private static readonly int ColorTagFullLength = "".Length;
+ private static readonly int ColorTagLeftLength = "".Length;
+
+ private static readonly int ItalicTagFullLength = "".Length;
+ private static readonly int ItalicTagLeftLength = "".Length;
+
+ private static readonly int BoldTagFullLength = "".Length;
+ private static readonly int BoldTagLeftLength = "".Length;
+
+ private readonly TypedEventHandler actualThemeChangedEventHandler;
+
+ ///
+ /// 构造一个新的呈现描述文本的文本块
+ ///
+ public HtmlDescriptionTextBlock()
+ {
+ this.DisableInteraction();
+
+ Content = new TextBlock()
+ {
+ TextWrapping = TextWrapping.Wrap,
+ Style = TextStyle,
+ };
+
+ actualThemeChangedEventHandler = OnActualThemeChanged;
+ ActualThemeChanged += actualThemeChangedEventHandler;
+ }
+
+ private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ TextBlock textBlock = (TextBlock)((HtmlDescriptionTextBlock)d).Content;
+ ReadOnlySpan description = (string)e.NewValue;
+
+ UpdateDescription(textBlock, description);
+ }
+
+ private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ TextBlock textBlock = (TextBlock)((HtmlDescriptionTextBlock)d).Content;
+ textBlock.Style = (Style)e.NewValue;
+ }
+
+ private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan description)
+ {
+ textBlock.Inlines.Clear();
+
+ int last = 0;
+ for (int i = 0; i < description.Length;)
+ {
+ // newline
+ if (description[i..].StartsWith(@"
"))
+ {
+ AppendText(textBlock, description[last..i]);
+ AppendLineBreak(textBlock);
+ i += 4;
+ last = i;
+ }
+
+ // color tag
+ else if (description[i..].StartsWith(" slice)
+ {
+ text.Inlines.Add(new Run { Text = slice.ToString() });
+ }
+
+ private static void AppendColorText(TextBlock text, in ReadOnlySpan slice, Rgba32 color)
+ {
+ Color targetColor;
+ if (ThemeHelper.IsDarkMode(text.ActualTheme))
+ {
+ targetColor = color;
+ }
+ else
+ {
+ // Make lighter in light mode
+ Hsl32 hsl = color.ToHsl();
+ hsl.L *= 0.3;
+ targetColor = Rgba32.FromHsl(hsl);
+ }
+
+ text.Inlines.Add(new Run
+ {
+ Text = slice.ToString(),
+ Foreground = new SolidColorBrush(targetColor),
+ });
+ }
+
+ private static void AppendBoldText(TextBlock text, in ReadOnlySpan slice)
+ {
+ text.Inlines.Add(new Run
+ {
+ Text = slice.ToString(),
+ FontWeight = FontWeights.Bold,
+ });
+ }
+
+ private static void AppendItalicText(TextBlock text, in ReadOnlySpan slice)
+ {
+ text.Inlines.Add(new Run
+ {
+ Text = slice.ToString(),
+ FontStyle = Windows.UI.Text.FontStyle.Italic,
+ });
+ }
+
+ private static void AppendLineBreak(TextBlock text)
+ {
+ text.Inlines.Add(new LineBreak());
+ }
+
+ private void OnActualThemeChanged(FrameworkElement sender, object args)
+ {
+ // Simply re-apply texts
+ UpdateDescription((TextBlock)Content, Description);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Theme/FontStyle.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Theme/FontStyle.xaml
index 15dcc105..fd4e6dce 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Theme/FontStyle.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Theme/FontStyle.xaml
@@ -1,4 +1,7 @@
-
+
ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans
ms-appx:///Resource/Font/CascadiaMono.ttf#Cascadia Mono, ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans
@@ -99,6 +102,7 @@
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingView.xaml.cs
index 8ba31d6e..243c674f 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingView.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingView.xaml.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI.Controls;
+using Snap.Hutao.Control;
namespace Snap.Hutao.View.Control;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml
index 6ebb8d96..f0169b2c 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml
@@ -1,86 +1,14 @@
-
-
-
-
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml.cs
index 38b7da79..04090b39 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Control/LoadingViewSlim.xaml.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI.Controls;
+using Snap.Hutao.Control;
namespace Snap.Hutao.View.Control;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/StaticWebview2ViewerSource.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/StaticWebview2ViewerSource.cs
new file mode 100644
index 00000000..f7eed347
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Control/StaticWebview2ViewerSource.cs
@@ -0,0 +1,16 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.UI.Xaml;
+using Snap.Hutao.ViewModel.User;
+
+namespace Snap.Hutao.View.Control;
+
+[DependencyProperty("Source", typeof(string))]
+internal sealed partial class StaticWebview2ViewerSource : DependencyObject, IWebViewerSource
+{
+ public string GetSource(UserAndUid userAndUid)
+ {
+ return Source;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml
index ae1290bc..43ff0e45 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml
@@ -2,8 +2,8 @@
x:Class="Snap.Hutao.View.Control.StatisticsCard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
- xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
+ xmlns:cwcont="using:CommunityToolkit.WinUI.Controls"
+ xmlns:cwconv="using:CommunityToolkit.WinUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shch="using:Snap.Hutao.Control.Helper"
@@ -78,7 +78,7 @@
-
@@ -238,11 +238,11 @@
HorizontalAlignment="Stretch"
IsPredictPullAvailable="{Binding IsPredictPullAvailable}"
SelectedIndex="0"/>
-
-
+
@@ -274,8 +274,8 @@
Text="{Binding MinOrangePullFormatted}"/>
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/Webview2Viewer.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml
similarity index 83%
rename from src/Snap.Hutao/Snap.Hutao/View/Control/Webview2Viewer.xaml
rename to src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml
index c4ee3aa3..3afabd6a 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Control/Webview2Viewer.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml
@@ -1,9 +1,8 @@
@@ -15,4 +14,4 @@
x:Name="WebView"
Margin="0"
DefaultBackgroundColor="Transparent"/>
-
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs
new file mode 100644
index 00000000..8b774118
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/View/Control/WebViewer.xaml.cs
@@ -0,0 +1,107 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.Mvvm.Messaging;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.Web.WebView2.Core;
+using Snap.Hutao.Message;
+using Snap.Hutao.Service.Notification;
+using Snap.Hutao.Service.User;
+using Snap.Hutao.ViewModel.User;
+using Snap.Hutao.Web.Bridge;
+using System.Diagnostics;
+
+namespace Snap.Hutao.View.Control;
+
+[DependencyProperty("SourceProvider", typeof(IWebViewerSource))]
+internal partial class WebViewer : UserControl, IRecipient
+{
+ private readonly IServiceProvider serviceProvider;
+ private readonly RoutedEventHandler loadEventHandler;
+ private readonly RoutedEventHandler unloadEventHandler;
+
+ [SuppressMessage("", "IDE0052")]
+ private MiHoYoJSInterface? jsInterface;
+
+ public WebViewer()
+ {
+ InitializeComponent();
+ serviceProvider = Ioc.Default;
+ serviceProvider.GetRequiredService().Register(this);
+
+ loadEventHandler = OnLoaded;
+ unloadEventHandler = OnUnloaded;
+
+ Loaded += loadEventHandler;
+ Unloaded += unloadEventHandler;
+ }
+
+ public void Receive(UserChangedMessage message)
+ {
+ if (message.NewValue?.SelectedUserGameRole is null)
+ {
+ return;
+ }
+
+ ITaskContext taskContext = serviceProvider.GetRequiredService();
+ taskContext.InvokeOnMainThread(RefreshWebview2Content);
+ }
+
+ private void OnLoaded(object sender, RoutedEventArgs e)
+ {
+ InitializeAsync().SafeForget();
+ }
+
+ private void OnUnloaded(object sender, RoutedEventArgs e)
+ {
+ jsInterface = null;
+ Loaded -= loadEventHandler;
+ Unloaded -= unloadEventHandler;
+ }
+
+ private async ValueTask InitializeAsync()
+ {
+ await WebView.EnsureCoreWebView2Async();
+ RefreshWebview2Content();
+ }
+
+ private async void RefreshWebview2Content()
+ {
+ User? user = serviceProvider.GetRequiredService().Current;
+ if (user is null)
+ {
+ return;
+ }
+
+ CoreWebView2 coreWebView2 = WebView.CoreWebView2;
+
+ if (SourceProvider is not null)
+ {
+ if (UserAndUid.TryFromUser(user, out UserAndUid? userAndUid))
+ {
+ string source = SourceProvider.GetSource(userAndUid);
+ if (!string.IsNullOrEmpty(source))
+ {
+ foreach (CoreWebView2Cookie cookie in await coreWebView2.CookieManager.GetCookiesAsync(".mihoyo.com"))
+ {
+ coreWebView2.CookieManager.DeleteCookie(cookie);
+ }
+
+ coreWebView2.SetCookie(user.CookieToken, user.LToken, user.SToken).SetMobileUserAgent();
+ jsInterface = serviceProvider.CreateInstance(coreWebView2, userAndUid);
+
+ CoreWebView2Navigator navigator = new(coreWebView2);
+ await navigator.NavigateAsync("about:blank");
+ Debug.WriteLine($"Before {source}");
+ await navigator.NavigateAsync(source);
+ Debug.WriteLine($"After {WebView.Source}");
+ }
+ }
+ else
+ {
+ serviceProvider.GetRequiredService().Warning(SH.MustSelectUserAndUid);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/Webview2Viewer.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/Webview2Viewer.xaml.cs
deleted file mode 100644
index 88eba28c..00000000
--- a/src/Snap.Hutao/Snap.Hutao/View/Control/Webview2Viewer.xaml.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.Web.WebView2.Core;
-using Snap.Hutao.Service.Notification;
-using Snap.Hutao.Service.User;
-using Snap.Hutao.ViewModel.User;
-using Snap.Hutao.Web.Bridge;
-
-namespace Snap.Hutao.View.Control;
-
-[DependencyProperty("Source", typeof(string), default(string)!, nameof(OnSourceChanged))]
-[DependencyProperty("User", typeof(User))]
-internal partial class Webview2Viewer : UserControl
-{
- private readonly IServiceProvider serviceProvider;
- private readonly RoutedEventHandler loadEventHandler;
- private readonly RoutedEventHandler unloadEventHandler;
-
- private MiHoYoJSInterface? jsInterface;
- private bool isInitialized;
-
- public Webview2Viewer()
- {
- InitializeComponent();
- serviceProvider = Ioc.Default;
-
- loadEventHandler = OnLoaded;
- unloadEventHandler = OnUnloaded;
-
- Loaded += loadEventHandler;
- Unloaded += unloadEventHandler;
- }
-
- private static void OnSourceChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
- {
- Webview2Viewer viewer = (Webview2Viewer)dp;
- if (viewer.isInitialized)
- {
- viewer.RefreshWebview2Content();
- }
- }
-
- private void OnLoaded(object sender, RoutedEventArgs e)
- {
- InitializeAsync().SafeForget();
- }
-
- private void OnUnloaded(object sender, RoutedEventArgs e)
- {
- jsInterface = null;
- Loaded -= loadEventHandler;
- Unloaded -= unloadEventHandler;
- }
-
- private async ValueTask InitializeAsync()
- {
- await WebView.EnsureCoreWebView2Async();
- RefreshWebview2Content();
- }
-
- private void RefreshWebview2Content()
- {
- if (User is null)
- {
- IUserService userService = serviceProvider.GetRequiredService();
- User ??= userService.Current;
- if (User is null)
- {
- return;
- }
- }
-
- CoreWebView2 coreWebView2 = WebView.CoreWebView2;
- if (UserAndUid.TryFromUser(User, out UserAndUid? userAndUid) && !string.IsNullOrEmpty(Source))
- {
- coreWebView2.SetCookie(User.CookieToken, User.LToken, User.SToken).SetMobileUserAgent();
- jsInterface = serviceProvider.CreateInstance(coreWebView2, userAndUid);
- coreWebView2.Navigate(Source);
- }
- else
- {
- serviceProvider.GetRequiredService().Warning(SH.MustSelectUserAndUid);
- }
-
- isInitialized = true;
- }
-}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityRevertConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityRevertConverter.cs
index 21c8e197..f4ec39d6 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityRevertConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/BoolToVisibilityRevertConverter.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI.Converters;
+using CommunityToolkit.WinUI.Converters;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.View.Converter;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToBoolConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToBoolConverter.cs
index 8f5d3b55..e80875e9 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToBoolConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToBoolConverter.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI.Converters;
+using CommunityToolkit.WinUI.Converters;
namespace Snap.Hutao.View.Converter;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToBoolRevertConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToBoolRevertConverter.cs
index a688bb5f..cbdd8947 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToBoolRevertConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToBoolRevertConverter.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI.Converters;
+using CommunityToolkit.WinUI.Converters;
namespace Snap.Hutao.View.Converter;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityConverter.cs
index e1492003..3a05915a 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityConverter.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI.Converters;
+using CommunityToolkit.WinUI.Converters;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.View.Converter;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityRevertConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityRevertConverter.cs
index 29b76368..aa56db4a 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityRevertConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityRevertConverter.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI.Converters;
+using CommunityToolkit.WinUI.Converters;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.View.Converter;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/StringBoolConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/StringBoolConverter.cs
index 8da48643..4c4db0db 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Converter/StringBoolConverter.cs
+++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/StringBoolConverter.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI.Converters;
+using CommunityToolkit.WinUI.Converters;
using Snap.Hutao.Control;
namespace Snap.Hutao.View.Converter;
diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
index d897540f..a652374d 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
@@ -67,11 +67,6 @@
-
-
-
+
@@ -53,7 +56,7 @@
DefaultLabelPosition="Right">
-
-
+
+
-
-
+
+
-
+
+
+
+
+
+
@@ -218,7 +226,24 @@
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
index eb9444ef..f476cc5d 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml
@@ -2,9 +2,9 @@
x:Class="Snap.Hutao.View.Page.AnnouncementPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:cw="using:CommunityToolkit.WinUI"
xmlns:cwa="using:CommunityToolkit.WinUI.Animations"
xmlns:cwb="using:CommunityToolkit.WinUI.Behaviors"
- xmlns:cwu="using:CommunityToolkit.WinUI.UI"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
@@ -12,6 +12,7 @@
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shca="using:Snap.Hutao.Control.Animation"
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:shvco="using:Snap.Hutao.View.Control"
@@ -48,8 +49,8 @@
-
-
+
+
@@ -167,13 +168,15 @@
-
-
+
+
1
-
-
+
+
0.5
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
-
+
-
-
+
-
+
@@ -422,18 +420,18 @@
0
0
-
-
-
+
-
+
-
-
+
@@ -721,7 +719,7 @@
-
@@ -778,7 +776,7 @@
-
+
@@ -804,7 +802,7 @@
-
+
@@ -832,8 +830,8 @@
-
-
+
+
@@ -851,19 +849,19 @@
Margin="0,24,0,0"
HorizontalAlignment="Center"
Spacing="{StaticResource SettingsCardSpacing}">
-
-
-
-
-
+
+
0.4
-
-
+
+
1
-
-
+
+
-
-
-
+
+
-
-
+
+
@@ -309,7 +307,7 @@
0
0
-
-
-
-
+
@@ -400,7 +398,7 @@
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageCultivationAddProjectContinue}"/>
-
-
+
@@ -484,7 +482,7 @@
ItemsSource="{Binding DailyNote.Expeditions, Mode=OneWay}">
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
deleted file mode 100644
index d5ed9b5b..00000000
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml
+++ /dev/null
@@ -1,323 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 16
- 0
- 0
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml.cs
deleted file mode 100644
index 92650314..00000000
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/HutaoDatabasePage.xaml.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Snap.Hutao.Control;
-using Snap.Hutao.ViewModel.Complex;
-
-namespace Snap.Hutao.View.Page;
-
-///
-/// 胡桃数据库页面
-///
-[HighQuality]
-internal sealed partial class HutaoDatabasePage : ScopedPage
-{
- ///
- /// 构造一个新的胡桃数据库页面
- ///
- public HutaoDatabasePage()
- {
- InitializeWith();
- InitializeComponent();
- }
-}
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
index 0f78a8bc..8fd3fe8e 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml
@@ -43,6 +43,10 @@
Margin="0,0,0,0"
Command="{Binding UpdateCheckCommand}"
Content="{shcm:ResourceString Name=ViewPageSettingUpdateCheckAction}"/>
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
- 0
- 0
- 0
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+ 0
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+ 16
+ 0
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml
index dd285d4a..d623c4fc 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/TestPage.xaml
@@ -32,7 +32,11 @@
-
+
+
+
+
+
@@ -53,7 +57,11 @@
-
+
+
+
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
index 7d1cf3ee..37e5af32 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml
@@ -2,7 +2,7 @@
x:Class="Snap.Hutao.View.Page.WikiAvatarPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
+ xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
@@ -99,13 +99,13 @@
Icon="{shcm:FontIcon Glyph=}"
Label="{shcm:ResourceString Name=ViewPageWiKiGeneralAddToDevPlanButtonLabel}"/>
-
-
+
+
-
-
+
+
-
@@ -236,9 +236,9 @@
Text="{Binding Selected.FetterInfo.BirthFormatted}"
TextWrapping="NoWrap"/>
-
+
-
@@ -274,7 +274,7 @@
Text="{Binding Selected.FetterInfo.CvKorean}"
TextWrapping="NoWrap"/>
-
+
@@ -366,11 +366,11 @@
-
-
-
+
+
+
0
-
+
-
-
-
+
+
+
1
-
+
-
-
-
+
+
+
2
-
+
-
-
+
+
@@ -585,8 +585,8 @@
-
-
+
+
-
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiMonsterPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiMonsterPage.xaml
index 3b0eaaa7..f9434cd6 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiMonsterPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiMonsterPage.xaml
@@ -2,8 +2,8 @@
x:Class="Snap.Hutao.View.Page.WikiMonsterPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:clw="using:CommunityToolkit.Labs.WinUI"
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
- xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
@@ -13,6 +13,7 @@
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shcp="using:Snap.Hutao.Control.Panel"
+ xmlns:shct="using:Snap.Hutao.Control.Text"
xmlns:shvc="using:Snap.Hutao.View.Control"
xmlns:shvw="using:Snap.Hutao.ViewModel.Wiki"
d:DataContext="{d:DesignInstance Type=shvw:WikiMonsterViewModel}"
@@ -55,8 +56,8 @@
-
-
+
+
+ Text="{Binding Name}"/>
@@ -108,18 +109,17 @@
VerticalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{Binding Selected.Title}"/>
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
@@ -172,8 +172,8 @@
-
-
+
+
-
+
-
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml
index 5e1e1242..24314272 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml
@@ -2,7 +2,7 @@
x:Class="Snap.Hutao.View.Page.WikiWeaponPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
+ xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
@@ -69,13 +69,13 @@
Icon="{shcm:FontIcon Glyph=}"
Label="{shcm:ResourceString Name=ViewPageWiKiGeneralAddToDevPlanButtonLabel}"/>
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
index bc7fdbf7..211896d4 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/UserView.xaml
@@ -16,31 +16,55 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs
index 2a9f56c8..a3b0bdbf 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementView.cs
@@ -55,7 +55,7 @@ internal sealed class AchievementView : ObservableObject, IEntityWithMetadata achievements, out List? combined)
+ private bool TryGetAchievements(EntityAchievementArchive archive, List achievements, [NotNullWhen(true)] out List? combined)
{
try
{
@@ -356,7 +360,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
{
if (goal is null)
{
- Achievements.Filter = null;
+ Achievements.Filter = default!;
}
else
{
@@ -378,16 +382,21 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
if (uint.TryParse(search, out uint achievementId))
{
Achievements.Filter = obj => ((AchievementView)obj).Inner.Id == achievementId;
+ return;
}
- else
+
+ if (VersionRegex().IsMatch(search))
{
- Achievements.Filter = obj =>
- {
- AchievementView view = (AchievementView)obj;
- return view.Inner.Title.Contains(search, StringComparison.CurrentCultureIgnoreCase)
- || view.Inner.Description.Contains(search, StringComparison.CurrentCultureIgnoreCase);
- };
+ Achievements.Filter = obj => ((AchievementView)obj).Inner.Version == search;
+ return;
}
+
+ Achievements.Filter = obj =>
+ {
+ AchievementView view = (AchievementView)obj;
+ return view.Inner.Title.Contains(search, StringComparison.CurrentCultureIgnoreCase)
+ || view.Inner.Description.Contains(search, StringComparison.CurrentCultureIgnoreCase);
+ };
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs
index 40d9f156..8bfb46c7 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Model;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Primitive;
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/HutaoDatabaseViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/HutaoDatabaseViewModel.cs
index 7a291a07..207df391 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/HutaoDatabaseViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Complex/HutaoDatabaseViewModel.cs
@@ -23,17 +23,24 @@ internal sealed partial class HutaoDatabaseViewModel : Abstraction.ViewModel
private List? avatarConstellationInfos;
private List? teamAppearances;
private Overview? overview;
+ private AvatarRankView? selectedAvatarUsageRank;
+ private AvatarRankView? selectedAvatarAppearanceRank;
+ private TeamAppearanceView? selectedTeamAppearance;
///
/// 角色使用率
///
public List? AvatarUsageRanks { get => avatarUsageRanks; set => SetProperty(ref avatarUsageRanks, value); }
+ public AvatarRankView? SelectedAvatarUsageRank { get => selectedAvatarUsageRank; set => SetProperty(ref selectedAvatarUsageRank, value); }
+
///
/// 角色上场率
///
public List? AvatarAppearanceRanks { get => avatarAppearanceRanks; set => SetProperty(ref avatarAppearanceRanks, value); }
+ public AvatarRankView? SelectedAvatarAppearanceRank { get => selectedAvatarAppearanceRank; set => SetProperty(ref selectedAvatarAppearanceRank, value); }
+
///
/// 角色命座信息
///
@@ -44,6 +51,8 @@ internal sealed partial class HutaoDatabaseViewModel : Abstraction.ViewModel
///
public List? TeamAppearances { get => teamAppearances; set => SetProperty(ref teamAppearances, value); }
+ public TeamAppearanceView? SelectedTeamAppearance { get => selectedTeamAppearance; set => SetProperty(ref selectedTeamAppearance, value); }
+
///
/// 总览数据
///
@@ -56,9 +65,15 @@ internal sealed partial class HutaoDatabaseViewModel : Abstraction.ViewModel
{
await taskContext.SwitchToMainThreadAsync();
AvatarAppearanceRanks = hutaoCache.AvatarAppearanceRanks;
+ SelectedAvatarAppearanceRank = AvatarAppearanceRanks?.FirstOrDefault();
+
AvatarUsageRanks = hutaoCache.AvatarUsageRanks;
- AvatarConstellationInfos = hutaoCache.AvatarConstellationInfos;
+ SelectedAvatarUsageRank = AvatarUsageRanks?.FirstOrDefault();
+
TeamAppearances = hutaoCache.TeamAppearances;
+ SelectedTeamAppearance = TeamAppearances?.FirstOrDefault();
+
+ AvatarConstellationInfos = hutaoCache.AvatarConstellationInfos;
Overview = hutaoCache.Overview;
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteViewModel.cs
index 7f781ce2..ee0071ea 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteViewModel.cs
@@ -9,9 +9,9 @@ using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.DailyNote;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User;
+using Snap.Hutao.View.Control;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.ViewModel.User;
-using Snap.Hutao.Web.Request.QueryString;
using System.Collections.ObjectModel;
namespace Snap.Hutao.ViewModel.DailyNote;
@@ -42,19 +42,7 @@ internal sealed partial class DailyNoteViewModel : Abstraction.ViewModel
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
- public string VerifyUrl
- {
- get
- {
- if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
- {
- QueryString query = userAndUid.Uid.ToQueryString();
- return $"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/?{query}";
- }
-
- return default!;
- }
- }
+ public IWebViewerSource VerifyUrlSource { get => new DailyNoteWebViewerSource(); }
///
/// 用户与角色集合
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs
new file mode 100644
index 00000000..1f7ef7f0
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/DailyNote/DailyNoteWebViewerSource.cs
@@ -0,0 +1,17 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.View.Control;
+using Snap.Hutao.ViewModel.User;
+using Snap.Hutao.Web.Request.QueryString;
+
+namespace Snap.Hutao.ViewModel.DailyNote;
+
+internal sealed class DailyNoteWebViewerSource : IWebViewerSource
+{
+ public string GetSource(UserAndUid userAndUid)
+ {
+ QueryString query = userAndUid.Uid.ToQueryString();
+ return $"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/?{query}";
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs
index 0622c80b..166bc902 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/GachaLog/GachaLogViewModel.cs
@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Microsoft.Graphics.Canvas.Svg;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.Database;
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/GuideViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/GuideViewModel.cs
index 0c82c32d..6f993bdd 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/GuideViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Guide/GuideViewModel.cs
@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.Mvvm.Messaging;
-using CommunityToolkit.WinUI.Notifications;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Setting;
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs
index e507753a..a937c793 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs
@@ -111,6 +111,12 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
AppInstance.Restart(string.Empty);
}
+ [Command("StoreReviewCommand")]
+ private static async Task StoreReviewAsync()
+ {
+ await Launcher.LaunchUriAsync(new("ms-windows-store://review/?ProductId=9PH4NXJ2JN52"));
+ }
+
[Command("SetGamePathCommand")]
private async Task SetGamePathAsync()
{
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/AvatarView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/AvatarView.cs
index a9a27914..494c238a 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/AvatarView.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/AvatarView.cs
@@ -5,7 +5,6 @@ using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
-using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.ViewModel.SpiralAbyss;
@@ -15,16 +14,6 @@ namespace Snap.Hutao.ViewModel.SpiralAbyss;
[HighQuality]
internal class AvatarView : INameIconSide, IMappingFrom
{
- ///
- /// 构造一个新的角色视图
- ///
- /// 角色Id
- /// Id角色映射
- public AvatarView(in AvatarId avatarId, Dictionary idAvatarMap)
- : this(idAvatarMap[avatarId])
- {
- }
-
///
/// 构造一个新的角色视图
///
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/BattleView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/BattleView.cs
index efb8f465..cff22872 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/BattleView.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/BattleView.cs
@@ -1,7 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Snap.Hutao.Model.Primitive;
+using Snap.Hutao.Core.Abstraction;
+using Snap.Hutao.Model;
+using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.SpiralAbyss;
namespace Snap.Hutao.ViewModel.SpiralAbyss;
@@ -10,26 +12,49 @@ namespace Snap.Hutao.ViewModel.SpiralAbyss;
/// 上下半视图
///
[HighQuality]
-internal sealed class BattleView
+internal sealed class BattleView : IMappingFrom
{
- ///
- /// 构造一个新的上下半视图
- ///
- /// 战斗
- /// Id角色映射
- public BattleView(Battle battle, Dictionary idAvatarMap)
+ private BattleView(TowerLevel towerLevel, uint battleIndex, SpiralAbyssMetadataContext context)
{
- Time = $"{DateTimeOffset.FromUnixTimeSeconds(battle.Timestamp).ToLocalTime():yyyy.MM.dd HH:mm:ss}";
- Avatars = battle.Avatars.SelectList(a => AvatarView.From(idAvatarMap[a.Id]));
+ IndexValue = battleIndex;
+ Gadget = battleIndex switch
+ {
+ 1U => towerLevel.FirstGadget,
+ 2U => towerLevel.SecondGadget,
+ _ => default,
+ };
+ MonsterWaves = battleIndex switch
+ {
+ 1U => towerLevel.FirstWaves.SelectList(w => BattleWave.From(w, context)),
+ 2U => towerLevel.SecondWaves.SelectList(w => BattleWave.From(w, context)),
+ _ => default!,
+ };
}
///
/// 时间
///
- public string Time { get; }
+ public string? Time { get; private set; }
///
/// 角色
///
- public List Avatars { get; }
+ public List? Avatars { get; private set; }
+
+ public NameDescription? Gadget { get; }
+
+ public List MonsterWaves { get; }
+
+ internal uint IndexValue { get; }
+
+ public static BattleView From(TowerLevel level, uint index, SpiralAbyssMetadataContext context)
+ {
+ return new(level, index, context);
+ }
+
+ public void WithSpiralAbyssBattle(Battle battle, SpiralAbyssMetadataContext context)
+ {
+ Time = $"{DateTimeOffset.FromUnixTimeSeconds(battle.Timestamp).ToLocalTime():yyyy.MM.dd HH:mm:ss}";
+ Avatars = battle.Avatars.SelectList(a => AvatarView.From(context.IdAvatarMap[a.Id]));
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/BattleWave.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/BattleWave.cs
new file mode 100644
index 00000000..ee121199
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/BattleWave.cs
@@ -0,0 +1,26 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.Abstraction;
+using Snap.Hutao.Model.Metadata;
+using Snap.Hutao.Model.Metadata.Tower;
+
+namespace Snap.Hutao.ViewModel.SpiralAbyss;
+
+internal sealed class BattleWave : IMappingFrom
+{
+ private BattleWave(TowerWave towerWave, SpiralAbyssMetadataContext context)
+ {
+ Description = towerWave.Type.GetLocalizedDescriptionOrDefault() ?? SH.ModelMetadataTowerWaveTypeDefault;
+ Monsters = towerWave.Monsters.SelectList(m => MonsterView.From(m, context.IdMonsterMap[MonsterRelationship.Normalize(m.Id)]));
+ }
+
+ public string Description { get; }
+
+ public List Monsters { get; }
+
+ public static BattleWave From(TowerWave tower, SpiralAbyssMetadataContext context)
+ {
+ return new(tower, context);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/FloorView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/FloorView.cs
index 369cc5f1..865b2126 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/FloorView.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/FloorView.cs
@@ -1,7 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Snap.Hutao.Model.Primitive;
+using Snap.Hutao.Core.Abstraction;
+using Snap.Hutao.Model.Metadata.Tower;
namespace Snap.Hutao.ViewModel.SpiralAbyss;
@@ -9,21 +10,19 @@ namespace Snap.Hutao.ViewModel.SpiralAbyss;
/// 层视图
///
[HighQuality]
-internal sealed class FloorView
+internal sealed class FloorView : IMappingFrom
{
- ///
- /// 构造一个新的层视图
- ///
- /// 层
- /// Id角色映射
- public FloorView(Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.Floor floor, Dictionary idAvatarMap)
+ public FloorView(TowerFloor floor, SpiralAbyssMetadataContext context)
{
Index = SH.ModelBindingHutaoComplexRankFloor.Format(floor.Index);
- SettleTime = $"{DateTimeOffset.FromUnixTimeSeconds(floor.SettleTime).ToLocalTime():yyyy.MM.dd HH:mm:ss}";
- Star = floor.Star;
- Levels = floor.Levels.SelectList(l => new LevelView(l, idAvatarMap));
+ IndexValue = floor.Index;
+ Disorders = floor.Descriptions;
+
+ Levels = context.IdLevelGroupMap[floor.LevelGroupId].SortBy(l => l.Index).SelectList(l => LevelView.From(l, context));
}
+ public bool Engaged { get; private set; }
+
///
/// 层号
///
@@ -32,15 +31,39 @@ internal sealed class FloorView
///
/// 时间
///
- public string SettleTime { get; }
+ public string? SettleTime { get; private set; }
///
/// 星数
///
- public int Star { get; }
+ public int Star { get; private set; }
+
+ public List Disorders { get; }
///
/// 间信息
///
public List Levels { get; }
+
+ internal uint IndexValue { get; }
+
+ public static FloorView From(TowerFloor floor, SpiralAbyssMetadataContext context)
+ {
+ return new(floor, context);
+ }
+
+ public void WithSpiralAbyssFloor(Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.Floor floor, SpiralAbyssMetadataContext context)
+ {
+ SettleTime = $"{DateTimeOffset.FromUnixTimeSeconds(floor.SettleTime).ToLocalTime():yyyy.MM.dd HH:mm:ss}";
+ Star = floor.Star;
+ Engaged = true;
+
+ foreach (LevelView levelView in Levels)
+ {
+ if (floor.Levels.SingleOrDefault(l => l.Index == levelView.IndexValue) is { } level)
+ {
+ levelView.WithSpiralAbyssLevel(level, context);
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/LevelView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/LevelView.cs
index ce475b48..5924b5a4 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/LevelView.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/LevelView.cs
@@ -1,7 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Snap.Hutao.Model.Primitive;
+using Snap.Hutao.Core.Abstraction;
+using Snap.Hutao.Model.Metadata.Tower;
namespace Snap.Hutao.ViewModel.SpiralAbyss;
@@ -9,18 +10,17 @@ namespace Snap.Hutao.ViewModel.SpiralAbyss;
/// 间视图
///
[HighQuality]
-internal sealed class LevelView
+internal sealed class LevelView : IMappingFrom
{
- ///
- /// 构造一个新的间视图
- ///
- /// 间
- /// Id角色映射
- public LevelView(Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.Level level, Dictionary idAvatarMap)
+ private LevelView(TowerLevel towerLevel, SpiralAbyssMetadataContext context)
{
- Index = SH.ModelBindingHutaoComplexRankLevel.Format(level.Index);
- Star = level.Star;
- Battles = level.Battles.SelectList(b => new BattleView(b, idAvatarMap));
+ Index = SH.ModelBindingHutaoComplexRankLevel.Format(towerLevel.Index);
+ IndexValue = towerLevel.Index;
+ Battles = new()
+ {
+ BattleView.From(towerLevel, 1, context),
+ BattleView.From(towerLevel, 2, context),
+ };
}
///
@@ -31,10 +31,30 @@ internal sealed class LevelView
///
/// 星数
///
- public int Star { get; }
+ public int Star { get; private set; }
///
/// 上下半
///
- public List Battles { get; }
+ public List Battles { get; private set; }
+
+ internal uint IndexValue { get; set; }
+
+ public static LevelView From(TowerLevel towerLevel, SpiralAbyssMetadataContext context)
+ {
+ return new(towerLevel, context);
+ }
+
+ public void WithSpiralAbyssLevel(Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.Level level, SpiralAbyssMetadataContext context)
+ {
+ Star = level.Star;
+
+ foreach (BattleView battleView in Battles)
+ {
+ if (level.Battles.SingleOrDefault(b => b.Index == battleView.IndexValue) is { } battle)
+ {
+ battleView.WithSpiralAbyssBattle(battle, context);
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/MonsterView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/MonsterView.cs
new file mode 100644
index 00000000..db18c685
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/MonsterView.cs
@@ -0,0 +1,38 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Core.Abstraction;
+using Snap.Hutao.Model;
+using Snap.Hutao.Model.Metadata.Converter;
+using Snap.Hutao.Model.Metadata.Tower;
+
+namespace Snap.Hutao.ViewModel.SpiralAbyss;
+
+internal sealed class MonsterView : INameIcon, IMappingFrom
+{
+ private MonsterView(TowerMonster towerMonster, Model.Metadata.Monster.Monster metaMonster)
+ {
+ Name = metaMonster.Name;
+ Icon = MonsterIconConverter.IconNameToUri(metaMonster.Icon);
+ Affixes = towerMonster.Affixes;
+ Count = (int)towerMonster.Count;
+ AttackMonolith = towerMonster.AttackMonolith;
+ }
+
+ public string Name { get; }
+
+ public Uri Icon { get; }
+
+ public List? Affixes { get; }
+
+ public int Count { get; }
+
+ public bool IsCountOne { get => Count == 1; }
+
+ public bool AttackMonolith { get; }
+
+ public static MonsterView From(TowerMonster tower, Model.Metadata.Monster.Monster meta)
+ {
+ return new MonsterView(tower, meta);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssMetadataContext.cs
new file mode 100644
index 00000000..a258da01
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssMetadataContext.cs
@@ -0,0 +1,22 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Metadata.Avatar;
+using Snap.Hutao.Model.Metadata.Monster;
+using Snap.Hutao.Model.Metadata.Tower;
+using Snap.Hutao.Model.Primitive;
+
+namespace Snap.Hutao.ViewModel.SpiralAbyss;
+
+internal sealed class SpiralAbyssMetadataContext
+{
+ public Dictionary IdScheduleMap { get; set; } = default!;
+
+ public Dictionary IdFloorMap { get; set; } = default!;
+
+ public Dictionary> IdLevelGroupMap { get; set; } = default!;
+
+ public Dictionary IdMonsterMap { get; set; } = default!;
+
+ public Dictionary IdAvatarMap { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssRecordViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssRecordViewModel.cs
index a81297e3..d45fa594 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssRecordViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssRecordViewModel.cs
@@ -3,13 +3,10 @@
using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Message;
-using Snap.Hutao.Model.Entity;
-using Snap.Hutao.Model.Metadata;
-using Snap.Hutao.Model.Primitive;
-using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.SpiralAbyss;
using Snap.Hutao.Service.User;
+using Snap.Hutao.ViewModel.Complex;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hutao.SpiralAbyss;
using Snap.Hutao.Web.Hutao.SpiralAbyss.Post;
@@ -27,42 +24,25 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
{
private readonly ISpiralAbyssRecordService spiralAbyssRecordService;
private readonly HomaSpiralAbyssClient spiralAbyssClient;
- private readonly IMetadataService metadataService;
private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext;
private readonly IUserService userService;
+ private readonly HutaoDatabaseViewModel hutaoDatabaseViewModel;
- private Dictionary? idAvatarMap;
- private ObservableCollection? spiralAbyssEntries;
- private SpiralAbyssEntry? selectedEntry;
- private SpiralAbyssView? spiralAbyssView;
+ private ObservableCollection? spiralAbyssEntries;
+ private SpiralAbyssView? selectedView;
///
/// 深渊记录
///
- public ObservableCollection? SpiralAbyssEntries { get => spiralAbyssEntries; set => SetProperty(ref spiralAbyssEntries, value); }
+ public ObservableCollection? SpiralAbyssEntries { get => spiralAbyssEntries; set => SetProperty(ref spiralAbyssEntries, value); }
///
/// 选中的深渊信息
///
- public SpiralAbyssEntry? SelectedEntry
- {
- get => selectedEntry; set
- {
- // We dont need to check the result here,
- // just refresh the view anyway.
- SetProperty(ref selectedEntry, value);
- if (value is not null && idAvatarMap is not null)
- {
- SpiralAbyssView = new(value.SpiralAbyss, idAvatarMap);
- }
- }
- }
+ public SpiralAbyssView? SelectedView { get => selectedView; set => SetProperty(ref selectedView, value); }
- ///
- /// 深渊的只读视图
- ///
- public SpiralAbyssView? SpiralAbyssView { get => spiralAbyssView; set => SetProperty(ref spiralAbyssView, value); }
+ public HutaoDatabaseViewModel HutaoDatabaseViewModel { get => hutaoDatabaseViewModel; }
///
public void Receive(UserChangedMessage message)
@@ -73,17 +53,14 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
}
else
{
- SpiralAbyssView = null;
+ SelectedView = null;
}
}
protected override async ValueTask InitializeUIAsync()
{
- if (await metadataService.InitializeAsync().ConfigureAwait(false))
+ if (await spiralAbyssRecordService.InitializeAsync().ConfigureAwait(false))
{
- idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
- idAvatarMap = AvatarIds.WithPlayers(idAvatarMap);
-
if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid))
{
await UpdateSpiralAbyssCollectionAsync(userAndUid).ConfigureAwait(false);
@@ -100,13 +77,13 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
private async ValueTask UpdateSpiralAbyssCollectionAsync(UserAndUid userAndUid)
{
- ObservableCollection? temp = null;
+ ObservableCollection? collection = null;
try
{
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
- temp = await spiralAbyssRecordService
- .GetSpiralAbyssCollectionAsync(userAndUid)
+ collection = await spiralAbyssRecordService
+ .GetSpiralAbyssViewCollectionAsync(userAndUid)
.ConfigureAwait(false);
}
}
@@ -115,8 +92,8 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
}
await taskContext.SwitchToMainThreadAsync();
- SpiralAbyssEntries = temp;
- SelectedEntry = SpiralAbyssEntries?.FirstOrDefault();
+ SpiralAbyssEntries = collection;
+ SelectedView = SpiralAbyssEntries?.FirstOrDefault();
}
[Command("RefreshCommand")]
@@ -140,7 +117,7 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
}
await taskContext.SwitchToMainThreadAsync();
- SelectedEntry = SpiralAbyssEntries.FirstOrDefault();
+ SelectedView = SpiralAbyssEntries.FirstOrDefault();
}
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs
index adf28d8f..5b65af15 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SpiralAbyss/SpiralAbyssView.cs
@@ -1,7 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using Snap.Hutao.Model.Primitive;
+using Snap.Hutao.Core.Abstraction;
+using Snap.Hutao.Model;
+using Snap.Hutao.Model.Entity;
+using Snap.Hutao.Model.Metadata.Tower;
namespace Snap.Hutao.ViewModel.SpiralAbyss;
@@ -9,27 +12,71 @@ namespace Snap.Hutao.ViewModel.SpiralAbyss;
/// 深渊视图
///
[HighQuality]
-internal sealed class SpiralAbyssView
+internal sealed class SpiralAbyssView : IEntityOnly,
+ IMappingFrom,
+ IMappingFrom
{
+ private readonly SpiralAbyssEntry? entity;
+
///
/// 构造一个新的深渊视图
///
- /// 深渊信息
+ /// 实体
/// Id角色映射
- public SpiralAbyssView(Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss spiralAbyss, Dictionary idAvatarMap)
+ private SpiralAbyssView(SpiralAbyssEntry entity, SpiralAbyssMetadataContext context)
+ : this(context.IdScheduleMap[entity.ScheduleId], context)
{
+ this.entity = entity;
+
+ Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss? spiralAbyss = entity.SpiralAbyss;
TotalBattleTimes = spiralAbyss.TotalBattleTimes;
TotalStar = spiralAbyss.TotalStar;
MaxFloor = spiralAbyss.MaxFloor;
- Reveals = spiralAbyss.RevealRank.SelectList(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId]));
- Defeat = spiralAbyss.DefeatRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
- Damage = spiralAbyss.DamageRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
- TakeDamage = spiralAbyss.TakeDamageRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
- NormalSkill = spiralAbyss.NormalSkillRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
- EnergySkill = spiralAbyss.EnergySkillRank.Select(r => new RankAvatar(r.Value, idAvatarMap[r.AvatarId])).SingleOrDefault();
- Floors = spiralAbyss.Floors.Select(f => new FloorView(f, idAvatarMap)).Reverse().ToList();
+ Reveals = spiralAbyss.RevealRank.SelectList(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId]));
+ Defeat = spiralAbyss.DefeatRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
+ Damage = spiralAbyss.DamageRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
+ TakeDamage = spiralAbyss.TakeDamageRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
+ NormalSkill = spiralAbyss.NormalSkillRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
+ EnergySkill = spiralAbyss.EnergySkillRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
+ Engaged = true;
+
+ foreach (Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.Floor webFloor in spiralAbyss.Floors)
+ {
+ // Ignoring floor 1 - 8 here
+ if (Floors.SingleOrDefault(f => f.IndexValue == webFloor.Index) is { } floor)
+ {
+ floor.WithSpiralAbyssFloor(webFloor, context);
+ }
+ }
}
+ private SpiralAbyssView(TowerSchedule towerSchedule, SpiralAbyssMetadataContext context)
+ {
+ ScheduleId = towerSchedule.Id;
+ TimeFormatted = $"{towerSchedule.Open:yyyy.MM.dd HH:mm} - {towerSchedule.Close:yyyy.MM.dd HH:mm}";
+
+ BlessingName = towerSchedule.BuffName;
+ Blessings = towerSchedule.Descriptions;
+ Floors = towerSchedule.FloorIds.Select(id => FloorView.From(context.IdFloorMap[id], context)).Reverse().ToList();
+ }
+
+ public uint ScheduleId { get; }
+
+ ///
+ /// 视图 中使用的计划 Id 字符串
+ ///
+ public string Schedule { get => SH.ModelEntitySpiralAbyssScheduleFormat.Format(ScheduleId); }
+
+ public SpiralAbyssEntry? Entity { get => entity; }
+
+ public string TimeFormatted { get; }
+
+ public string BlessingName { get; }
+
+ public List Blessings { get; }
+
+ public bool Engaged { get; }
+
///
/// 战斗次数
///
@@ -43,12 +90,12 @@ internal sealed class SpiralAbyssView
///
/// 最深抵达
///
- public string MaxFloor { get; }
+ public string MaxFloor { get; } = default!;
///
/// 出战次数
///
- public List Reveals { get; }
+ public List Reveals { get; } = default!;
///
/// 击破次数
@@ -79,4 +126,21 @@ internal sealed class SpiralAbyssView
/// 层信息
///
public List Floors { get; }
+
+ public static SpiralAbyssView From(SpiralAbyssEntry entity, SpiralAbyssMetadataContext context)
+ {
+ return new(entity, context);
+ }
+
+ public static SpiralAbyssView From(SpiralAbyssEntry? entity, TowerSchedule meta, SpiralAbyssMetadataContext context)
+ {
+ if (entity is not null)
+ {
+ return new(entity, context);
+ }
+ else
+ {
+ return new(meta, context);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs
index 25c508b9..f38f4a7c 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs
@@ -14,6 +14,7 @@ using Snap.Hutao.View.Page;
using Snap.Hutao.Web.Hoyolab;
using System.Collections.ObjectModel;
using System.Text;
+using Windows.System;
namespace Snap.Hutao.ViewModel.User;
@@ -25,6 +26,7 @@ namespace Snap.Hutao.ViewModel.User;
[Injection(InjectAs.Singleton)]
internal sealed partial class UserViewModel : ObservableObject
{
+ private readonly IDocumentationProvider documentationProvider;
private readonly INavigationService navigationService;
private readonly IServiceProvider serviceProvider;
private readonly IInfoBarService infoBarService;
@@ -248,4 +250,10 @@ internal sealed partial class UserViewModel : ObservableObject
}
}
}
+
+ [Command("OpenDocumentationCommand")]
+ private async Task OpenDocumentationAsync()
+ {
+ await Launcher.LaunchUriAsync(new(documentationProvider.GetDocumentation()));
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs
index 808abab5..b34762c1 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI;
+using CommunityToolkit.WinUI.Collections;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Entity.Primitive;
@@ -220,7 +220,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
Avatars.Filter = AvatarFilter.Compile(input);
- if (Avatars.Contains(Selected))
+ if (Selected is not null && Avatars.Contains(Selected))
{
return;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs
index 593f6d2d..b967965c 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI;
+using CommunityToolkit.WinUI.Collections;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs
index 298fa8e8..d832ca06 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs
@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.WinUI.UI;
+using CommunityToolkit.WinUI.Collections;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Entity.Primitive;
@@ -203,7 +203,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
Weapons.Filter = WeaponFilter.Compile(input);
- if (Weapons.Contains(Selected))
+ if (Selected is not null && Weapons.Contains(Selected))
{
return;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Navigator.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Navigator.cs
new file mode 100644
index 00000000..754ae587
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/CoreWebView2Navigator.cs
@@ -0,0 +1,34 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.Web.WebView2.Core;
+using Windows.Foundation;
+
+namespace Snap.Hutao.Web.Bridge;
+
+internal sealed class CoreWebView2Navigator
+{
+ private readonly TypedEventHandler coreWebView2NavigationCompletedEventHandler;
+ private readonly CoreWebView2 coreWebView2;
+ private TaskCompletionSource navigationTask = new();
+
+ public CoreWebView2Navigator(CoreWebView2 coreWebView2)
+ {
+ coreWebView2NavigationCompletedEventHandler = OnWebviewNavigationCompleted;
+ this.coreWebView2 = coreWebView2;
+ }
+
+ public async ValueTask NavigateAsync(string url)
+ {
+ coreWebView2.NavigationCompleted += coreWebView2NavigationCompletedEventHandler;
+ coreWebView2.Navigate(url);
+ await navigationTask.Task.ConfigureAwait(false);
+ coreWebView2.NavigationCompleted -= coreWebView2NavigationCompletedEventHandler;
+ }
+
+ private void OnWebviewNavigationCompleted(CoreWebView2 webView2, CoreWebView2NavigationCompletedEventArgs args)
+ {
+ navigationTask.TrySetResult();
+ navigationTask = new();
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
index bc81a941..22dbc19d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/MiHoYoJSInterface.cs
@@ -98,7 +98,7 @@ internal class MiHoYoJSInterface
{
{ "x-rpc-client_type", "5" },
{ "x-rpc-device_id", HoyolabOptions.DeviceId },
- { "x-rpc-app_version", HoyolabOptions.XrpcVersion },
+ { "x-rpc-app_version", SaltConstants.CNVersion },
},
};
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs
index 17e632f0..4321bec2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJSInterfaceOversea.cs
@@ -38,7 +38,7 @@ internal sealed class SignInJSInterfaceOversea : MiHoYoJSInterface
{
{ "x-rpc-client_type", "2" },
{ "x-rpc-device_id", HoyolabOptions.DeviceId },
- { "x-rpc-app_version", HoyolabOptions.XrpcVersionOversea },
+ { "x-rpc-app_version", SaltConstants.OSVersion },
},
};
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs
index 829958b2..a7fbf6ff 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Bridge/SignInJsInterface.cs
@@ -29,7 +29,7 @@ internal sealed class SignInJsInterface : MiHoYoJSInterface
{
{ "x-rpc-client_type", "2" },
{ "x-rpc-device_id", HoyolabOptions.DeviceId },
- { "x-rpc-app_version", HoyolabOptions.XrpcVersion },
+ { "x-rpc-app_version", SaltConstants.CNVersion },
},
};
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs
index c509d688..984c105c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabOptions.cs
@@ -16,45 +16,35 @@ internal sealed class HoyolabOptions : IOptions
///
/// 米游社请求UA
///
- public const string UserAgent = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/{XrpcVersion}";
+ public const string UserAgent = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/{SaltConstants.CNVersion}";
///
/// Hoyolab 请求UA
///
- public const string UserAgentOversea = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBSOversea/{XrpcVersionOversea}";
+ public const string UserAgentOversea = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBSOversea/{SaltConstants.OSVersion}";
///
/// 米游社移动端请求UA
///
- public const string MobileUserAgent = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/{XrpcVersion}";
+ public const string MobileUserAgent = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/{SaltConstants.CNVersion}";
///
/// Hoyolab 移动端请求UA
///
- public const string MobileUserAgentOversea = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBSOversea/{XrpcVersionOversea}";
-
- ///
- /// 米游社 Rpc 版本
- ///
- public const string XrpcVersion = "2.52.1";
-
- ///
- /// Hoyolab Rpc 版本
- ///
- public const string XrpcVersionOversea = "2.31.0";
+ public const string MobileUserAgentOversea = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBSOversea/{SaltConstants.OSVersion}";
private static readonly ImmutableDictionary SaltsInner = new Dictionary()
{
// Chinese
- [SaltType.K2] = "HiwYTTu2ovGcU51ehSXfe22SpNmQumlT",
- [SaltType.LK2] = "QCRgj6bHHQvS0Rz03loexYSXpuiO3DZ6",
+ [SaltType.K2] = SaltConstants.CNK2,
+ [SaltType.LK2] = SaltConstants.CNLK2,
[SaltType.X4] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
[SaltType.X6] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
[SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
// Oversea
- [SaltType.OSK2] = "599uqkwc0dlqu3h6epzjzfhgyyrd44ae",
- [SaltType.OSLK2] = "rk4xg2hakoi26nljpr099fv9fck1ah10",
+ [SaltType.OSK2] = SaltConstants.OSK2,
+ [SaltType.OSLK2] = SaltConstants.OSLK2,
[SaltType.OSX4] = "h4c1d6ywfq5bsbnbhm1bzq7bxzzv6srt",
[SaltType.OSX6] = "okr4obncj8bw5a65hbnn5oo6ixjc3l9w",
}.ToImmutableDictionary();
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Floor.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Floor.cs
index 8e728a5f..a53bfc3e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Floor.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Floor.cs
@@ -13,7 +13,7 @@ internal sealed class Floor
/// 层号
///
[JsonPropertyName("index")]
- public int Index { get; set; }
+ public uint Index { get; set; }
///
/// 图标
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Level.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Level.cs
index 03be351c..c3b637bc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Level.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/Level.cs
@@ -13,7 +13,7 @@ internal sealed class Level
/// 索引
///
[JsonPropertyName("index")]
- public int Index { get; set; }
+ public uint Index { get; set; }
///
/// 星数
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/SpiralAbyss.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/SpiralAbyss.cs
index 9f1c1cab..3d3a9244 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/SpiralAbyss.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/SpiralAbyss/SpiralAbyss.cs
@@ -13,7 +13,7 @@ internal sealed class SpiralAbyss
/// 计划Id
///
[JsonPropertyName("schedule_id")]
- public int ScheduleId { get; set; }
+ public uint ScheduleId { get; set; }
///
/// 开始时间
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs
index 2108f3c0..199c205b 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/Geetest/HomaGeetestClient.cs
@@ -3,8 +3,6 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Service;
-using Snap.Hutao.Service.Hutao;
-using Snap.Hutao.Web.Hutao.GachaLog;
using System.Net.Http;
namespace Snap.Hutao.Web.Hutao.Geetest;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaPassportClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaPassportClient.cs
index 3e1741c5..0b8d64ab 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaPassportClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaPassportClient.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
-using Snap.Hutao.Web.Hutao.SpiralAbyss;
using Snap.Hutao.Web.Response;
using System.Net.Http;
using System.Security.Cryptography;
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAsAService/Announcement.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAsAService/Announcement.cs
index 850142a3..197a39dc 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAsAService/Announcement.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HutaoAsAService/Announcement.cs
@@ -23,7 +23,7 @@ internal sealed class Announcement : UploadAnnouncement
///
public long LastUpdateTime { get; set; }
- public string UpdateTimeFormatted { get => $"{DateTimeOffset.FromUnixTimeSeconds(LastUpdateTime).ToLocalTime():yyyy-MM-dd HH:mm:ss}"; }
+ public string UpdateTimeFormatted { get => $"{DateTimeOffset.FromUnixTimeSeconds(LastUpdateTime).ToLocalTime():yyyy.MM.dd HH:mm:ss}"; }
public ICommand? DismissCommand { get; set; }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleFloor.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleFloor.cs
index 479d9af8..030ebbd2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleFloor.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleFloor.cs
@@ -25,7 +25,7 @@ internal sealed class SimpleFloor
///
/// 层遍号 1-12|9-12
///
- public int Index { get; set; }
+ public uint Index { get; set; }
///
/// 星数
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleLevel.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleLevel.cs
index 02bfae2c..3a4793b0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleLevel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleLevel.cs
@@ -25,7 +25,7 @@ internal sealed class SimpleLevel
///
/// 间遍号 1-3
///
- public int Index { get; set; }
+ public uint Index { get; set; }
///
/// 星数
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleSpiralAbyss.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleSpiralAbyss.cs
index b1d70c8d..26e25867 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleSpiralAbyss.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/SpiralAbyss/Post/SimpleSpiralAbyss.cs
@@ -26,7 +26,7 @@ internal sealed class SimpleSpiralAbyss
///
/// 计划Id
///
- public int ScheduleId { get; set; }
+ public uint ScheduleId { get; set; }
///
/// 总战斗次数