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; } /// /// 总战斗次数