mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
implement gradient control
This commit is contained in:
64
src/Snap.Hutao/Snap.Hutao/Context/FileSystem/CacheContext.cs
Normal file
64
src/Snap.Hutao/Snap.Hutao/Context/FileSystem/CacheContext.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Context.FileSystem.Location;
|
||||||
|
using Windows.Storage;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Context.FileSystem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 缓存目录上下文
|
||||||
|
/// </summary>
|
||||||
|
[Injection(InjectAs.Transient)]
|
||||||
|
internal class CacheContext : FileSystemContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的缓存目录上下文
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cache">缓存位置</param>
|
||||||
|
public CacheContext(Cache cache)
|
||||||
|
: base(cache)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取缓存文件夹
|
||||||
|
/// </summary>
|
||||||
|
public static StorageFolder Folder
|
||||||
|
{
|
||||||
|
get => ApplicationData.Current.TemporaryFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取缓存文件的名称
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uri">uri</param>
|
||||||
|
/// <returns>缓存文件的名称</returns>
|
||||||
|
public static string GetCacheFileName(Uri uri)
|
||||||
|
{
|
||||||
|
return CreateHash64(uri.ToString()).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取缓存文件的名称
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">url</param>
|
||||||
|
/// <returns>缓存文件的名称</returns>
|
||||||
|
public static string GetCacheFileName(string url)
|
||||||
|
{
|
||||||
|
return CreateHash64(url).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong CreateHash64(string str)
|
||||||
|
{
|
||||||
|
byte[] utf8 = System.Text.Encoding.UTF8.GetBytes(str);
|
||||||
|
|
||||||
|
ulong value = (ulong)utf8.Length;
|
||||||
|
for (int n = 0; n < utf8.Length; n++)
|
||||||
|
{
|
||||||
|
value += (ulong)utf8[n] << ((n * 5) % 56);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Context.FileSystem.Location;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 缓存位置
|
||||||
|
/// </summary>
|
||||||
|
[Injection(InjectAs.Transient)]
|
||||||
|
internal class Cache : IFileSystemLocation
|
||||||
|
{
|
||||||
|
private string? path;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string GetPath()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
path = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Context.FileSystem.Location;
|
|||||||
/// 我的文档位置
|
/// 我的文档位置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injection(InjectAs.Transient)]
|
[Injection(InjectAs.Transient)]
|
||||||
public class Metadata : IFileSystemLocation
|
internal class Metadata : IFileSystemLocation
|
||||||
{
|
{
|
||||||
private string? path;
|
private string? path;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Context.FileSystem.Location;
|
|||||||
/// 我的文档位置
|
/// 我的文档位置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injection(InjectAs.Transient)]
|
[Injection(InjectAs.Transient)]
|
||||||
public class MyDocument : IFileSystemLocation
|
internal class MyDocument : IFileSystemLocation
|
||||||
{
|
{
|
||||||
private string? path;
|
private string? path;
|
||||||
|
|
||||||
|
|||||||
140
src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs
Normal file
140
src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using CommunityToolkit.WinUI.Helpers;
|
||||||
|
using CommunityToolkit.WinUI.UI.Animations;
|
||||||
|
using Microsoft.UI;
|
||||||
|
using Microsoft.UI.Composition;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Hosting;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Media.Animation;
|
||||||
|
using Snap.Hutao.Context.FileSystem;
|
||||||
|
using Snap.Hutao.Core;
|
||||||
|
using Snap.Hutao.Core.Threading;
|
||||||
|
using Snap.Hutao.Extension;
|
||||||
|
using System.Numerics;
|
||||||
|
using Windows.Graphics.Imaging;
|
||||||
|
using Windows.Storage;
|
||||||
|
using Windows.Storage.Streams;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Control.Image;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支持渐变的图像
|
||||||
|
/// </summary>
|
||||||
|
public class Gradient : Microsoft.UI.Xaml.Controls.Control
|
||||||
|
{
|
||||||
|
private static readonly DependencyProperty SourceProperty = Property<Gradient>.Depend(nameof(Source), string.Empty, OnSourceChanged);
|
||||||
|
private static readonly ConcurrentCancellationTokenSource<Gradient> ImageLoading = new();
|
||||||
|
private SpriteVisual? spriteVisual;
|
||||||
|
private double imageAspectRatio;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的渐变图像
|
||||||
|
/// </summary>
|
||||||
|
public Gradient()
|
||||||
|
{
|
||||||
|
SizeChanged += OnSizeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 源
|
||||||
|
/// </summary>
|
||||||
|
public string Source
|
||||||
|
{
|
||||||
|
get => (string)GetValue(SourceProperty);
|
||||||
|
set => SetValue(SourceProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<StorageFile> GetCachedFileAsync(string url, CancellationToken token)
|
||||||
|
{
|
||||||
|
string fileName = CacheContext.GetCacheFileName(url);
|
||||||
|
CacheContext cacheContext = Ioc.Default.GetRequiredService<CacheContext>();
|
||||||
|
|
||||||
|
StorageFile storageFile;
|
||||||
|
if (!cacheContext.FileExists(fileName))
|
||||||
|
{
|
||||||
|
storageFile = await CacheContext.Folder.CreateFileAsync(fileName).AsTask(token);
|
||||||
|
await StreamHelper.GetHttpStreamToStorageFileAsync(new(url), storageFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
storageFile = await CacheContext.Folder.GetFileAsync(fileName).AsTask(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return storageFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
|
||||||
|
{
|
||||||
|
Gradient gradient = (Gradient)sender;
|
||||||
|
string url = (string)arg.NewValue;
|
||||||
|
|
||||||
|
gradient.ApplyImageAsync(url, ImageLoading.Register(gradient)).SafeForget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.NewSize != e.PreviousSize && spriteVisual is not null)
|
||||||
|
{
|
||||||
|
UpdateVisual(spriteVisual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateVisual(SpriteVisual spriteVisual)
|
||||||
|
{
|
||||||
|
if (spriteVisual is not null)
|
||||||
|
{
|
||||||
|
double width = ActualWidth;
|
||||||
|
double height = Math.Clamp(width * imageAspectRatio, 0, MaxHeight);
|
||||||
|
|
||||||
|
spriteVisual.Size = new Vector2((float)width, (float)height);
|
||||||
|
Height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyImageAsync(string url, CancellationToken token)
|
||||||
|
{
|
||||||
|
await AnimationBuilder
|
||||||
|
.Create()
|
||||||
|
.Opacity(0, 1)
|
||||||
|
.StartAsync(this, token);
|
||||||
|
|
||||||
|
StorageFile storageFile = await GetCachedFileAsync(url, token);
|
||||||
|
|
||||||
|
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||||
|
|
||||||
|
LoadedImageSurface imageSurface = await LoadImageSurfaceAsync(storageFile, token);
|
||||||
|
|
||||||
|
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.UniformToFill, vRatio: 0f);
|
||||||
|
|
||||||
|
CompositionLinearGradientBrush backgroundBrush = compositor.CompositeLinearGradientBrush(new(1f, 0), Vector2.UnitY, new(0, Colors.White), new(1, Colors.Black));
|
||||||
|
CompositionLinearGradientBrush foregroundBrush = compositor.CompositeLinearGradientBrush(Vector2.Zero, Vector2.UnitY, new(0, Colors.White), new(0.95f, Colors.Black));
|
||||||
|
|
||||||
|
CompositionEffectBrush gradientEffectBrush = compositor.CompositeBlendEffectBrush(backgroundBrush, foregroundBrush);
|
||||||
|
CompositionEffectBrush opacityMaskEffectBrush = compositor.CompositeLuminanceToAlphaEffectBrush(gradientEffectBrush);
|
||||||
|
CompositionEffectBrush alphaMaskEffectBrush = compositor.CompositeAlphaMaskEffectBrush(imageSurfaceBrush, opacityMaskEffectBrush);
|
||||||
|
|
||||||
|
spriteVisual = compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
|
||||||
|
UpdateVisual(spriteVisual);
|
||||||
|
|
||||||
|
ElementCompositionPreview.SetElementChildVisual(this, spriteVisual);
|
||||||
|
|
||||||
|
await AnimationBuilder
|
||||||
|
.Create()
|
||||||
|
.Opacity(1, 0)
|
||||||
|
.StartAsync(this, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
|
||||||
|
{
|
||||||
|
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token))
|
||||||
|
{
|
||||||
|
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream).AsTask(token);
|
||||||
|
imageAspectRatio = (double)decoder.PixelHeight / decoder.PixelWidth;
|
||||||
|
|
||||||
|
return LoadedImageSurface.StartLoadFromStream(imageStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,4 +22,9 @@ internal static class EventIds
|
|||||||
/// Forget任务执行异常
|
/// Forget任务执行异常
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly EventId TaskException = new(100002, nameof(TaskException));
|
public static readonly EventId TaskException = new(100002, nameof(TaskException));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forget任务执行异常
|
||||||
|
/// </summary>
|
||||||
|
public static readonly EventId AsyncCommandException = new(100003, nameof(AsyncCommandException));
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 并发<see cref="CancellationTokenSource"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TItem">项类型</typeparam>
|
||||||
|
internal class ConcurrentCancellationTokenSource<TItem>
|
||||||
|
where TItem : notnull
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<TItem, CancellationTokenSource> waitingItems = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 未某个项注册取消令牌
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">项</param>
|
||||||
|
/// <returns>取消令牌</returns>
|
||||||
|
public CancellationToken Register(TItem item)
|
||||||
|
{
|
||||||
|
if (waitingItems.TryRemove(item, out CancellationTokenSource? prevSource))
|
||||||
|
{
|
||||||
|
prevSource.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
CancellationTokenSource current = waitingItems.GetOrAdd(item, new CancellationTokenSource());
|
||||||
|
|
||||||
|
return current.Token;
|
||||||
|
}
|
||||||
|
}
|
||||||
154
src/Snap.Hutao/Snap.Hutao/Extension/CompositionExtensions.cs
Normal file
154
src/Snap.Hutao/Snap.Hutao/Extension/CompositionExtensions.cs
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.Graphics.Canvas.Effects;
|
||||||
|
using Microsoft.UI.Composition;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Extension;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 合成扩展
|
||||||
|
/// </summary>
|
||||||
|
internal static class CompositionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 创建拼合图视觉对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="compositor">合成器</param>
|
||||||
|
/// <param name="brush">画刷</param>
|
||||||
|
/// <returns>拼合图视觉对象</returns>
|
||||||
|
public static SpriteVisual CompositeSpriteVisual(this Compositor compositor, CompositionBrush brush)
|
||||||
|
{
|
||||||
|
SpriteVisual spriteVisual = compositor.CreateSpriteVisual();
|
||||||
|
spriteVisual.Brush = brush;
|
||||||
|
return spriteVisual;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建混合效果画刷
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="compositor">合成器</param>
|
||||||
|
/// <param name="background">前景</param>
|
||||||
|
/// <param name="foreground">背景</param>
|
||||||
|
/// <param name="blendEffectMode">混合模式</param>
|
||||||
|
/// <returns>合成效果画刷</returns>
|
||||||
|
public static CompositionEffectBrush CompositeBlendEffectBrush(
|
||||||
|
this Compositor compositor,
|
||||||
|
CompositionBrush background,
|
||||||
|
CompositionBrush foreground,
|
||||||
|
BlendEffectMode blendEffectMode = BlendEffectMode.Multiply)
|
||||||
|
{
|
||||||
|
BlendEffect effect = new()
|
||||||
|
{
|
||||||
|
Background = new CompositionEffectSourceParameter("Background"),
|
||||||
|
Foreground = new CompositionEffectSourceParameter("Foreground"),
|
||||||
|
Mode = blendEffectMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
|
||||||
|
|
||||||
|
brush.SetSourceParameter("Background", background);
|
||||||
|
brush.SetSourceParameter("Foreground", foreground);
|
||||||
|
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建亮度转不透明度效果画刷
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="compositor">合成器</param>
|
||||||
|
/// <param name="sourceBrush">源</param>
|
||||||
|
/// <returns>合成效果画刷</returns>
|
||||||
|
public static CompositionEffectBrush CompositeLuminanceToAlphaEffectBrush(this Compositor compositor, CompositionBrush sourceBrush)
|
||||||
|
{
|
||||||
|
LuminanceToAlphaEffect effect = new()
|
||||||
|
{
|
||||||
|
Source = new CompositionEffectSourceParameter("Source"),
|
||||||
|
};
|
||||||
|
|
||||||
|
CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush();
|
||||||
|
|
||||||
|
brush.SetSourceParameter("Source", sourceBrush);
|
||||||
|
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建不透明度蒙版效果画刷
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="compositor">合成器</param>
|
||||||
|
/// <param name="sourceBrush">源</param>
|
||||||
|
/// <param name="alphaMask">不透明度蒙版</param>
|
||||||
|
/// <returns>合成效果画刷</returns>
|
||||||
|
public static CompositionEffectBrush CompositeAlphaMaskEffectBrush(
|
||||||
|
this Compositor compositor,
|
||||||
|
CompositionBrush sourceBrush,
|
||||||
|
CompositionBrush alphaMask)
|
||||||
|
{
|
||||||
|
AlphaMaskEffect maskEffect = new()
|
||||||
|
{
|
||||||
|
AlphaMask = new CompositionEffectSourceParameter("AlphaMask"),
|
||||||
|
Source = new CompositionEffectSourceParameter("Source"),
|
||||||
|
};
|
||||||
|
|
||||||
|
CompositionEffectBrush brush = compositor.CreateEffectFactory(maskEffect).CreateBrush();
|
||||||
|
|
||||||
|
brush.SetSourceParameter("AlphaMask", alphaMask);
|
||||||
|
brush.SetSourceParameter("Source", sourceBrush);
|
||||||
|
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建一个表面画刷
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="compositor">合成器</param>
|
||||||
|
/// <param name="surface">合成表面</param>
|
||||||
|
/// <param name="stretch">拉伸方法</param>
|
||||||
|
/// <param name="hRatio">水平对齐比</param>
|
||||||
|
/// <param name="vRatio">垂直对齐比</param>
|
||||||
|
/// <returns>合成表面画刷</returns>
|
||||||
|
public static CompositionSurfaceBrush CompositeSurfaceBrush(
|
||||||
|
this Compositor compositor,
|
||||||
|
ICompositionSurface surface,
|
||||||
|
CompositionStretch stretch = CompositionStretch.None,
|
||||||
|
float hRatio = 0.5f,
|
||||||
|
float vRatio = 0.5f)
|
||||||
|
{
|
||||||
|
CompositionSurfaceBrush brush = compositor.CreateSurfaceBrush(surface);
|
||||||
|
brush.Stretch = stretch;
|
||||||
|
brush.VerticalAlignmentRatio = vRatio;
|
||||||
|
brush.HorizontalAlignmentRatio = hRatio;
|
||||||
|
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建一个线性渐变画刷
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="compositor">合成器</param>
|
||||||
|
/// <param name="start">起点</param>
|
||||||
|
/// <param name="end">终点</param>
|
||||||
|
/// <param name="stops">锚点</param>
|
||||||
|
/// <returns>线性渐变画刷</returns>
|
||||||
|
public static CompositionLinearGradientBrush CompositeLinearGradientBrush(
|
||||||
|
this Compositor compositor,
|
||||||
|
Vector2 start,
|
||||||
|
Vector2 end,
|
||||||
|
params GradientStop[] stops)
|
||||||
|
{
|
||||||
|
CompositionLinearGradientBrush brush = compositor.CreateLinearGradientBrush();
|
||||||
|
brush.StartPoint = start;
|
||||||
|
brush.EndPoint = end;
|
||||||
|
|
||||||
|
foreach (GradientStop stop in stops)
|
||||||
|
{
|
||||||
|
brush.ColorStops.Add(compositor.CreateColorGradientStop(stop.Offset, stop.Color));
|
||||||
|
}
|
||||||
|
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct GradientStop(float Offset, Windows.UI.Color Color);
|
||||||
|
}
|
||||||
@@ -24,6 +24,9 @@ public static class TaskExtensions
|
|||||||
{
|
{
|
||||||
await task.ConfigureAwait(continueOnCapturedContext);
|
await task.ConfigureAwait(continueOnCapturedContext);
|
||||||
}
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
logger?.LogError(EventIds.TaskException, e, "{caller}:{exception}", nameof(SafeForget), e.GetBaseException());
|
logger?.LogError(EventIds.TaskException, e, "{caller}:{exception}", nameof(SafeForget), e.GetBaseException());
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Microsoft.AppCenter.Crashes;
|
using Snap.Hutao.Core.Logging;
|
||||||
using Snap.Hutao.Factory.Abstraction;
|
using Snap.Hutao.Factory.Abstraction;
|
||||||
|
|
||||||
namespace Snap.Hutao.Factory;
|
namespace Snap.Hutao.Factory;
|
||||||
@@ -93,8 +93,7 @@ internal class AsyncRelayCommandFactory : IAsyncRelayCommandFactory
|
|||||||
if (asyncRelayCommand.ExecutionTask?.Exception is AggregateException exception)
|
if (asyncRelayCommand.ExecutionTask?.Exception is AggregateException exception)
|
||||||
{
|
{
|
||||||
Exception baseException = exception.GetBaseException();
|
Exception baseException = exception.GetBaseException();
|
||||||
logger.LogError(baseException, "异步命令发生了错误");
|
logger.LogError(EventIds.AsyncCommandException, baseException, "异步命令发生了错误");
|
||||||
Crashes.TrackError(baseException);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ internal static class IocHttpClientConfiguration
|
|||||||
services.AddHttpClient<MetadataService>(DefaultConfiguration);
|
services.AddHttpClient<MetadataService>(DefaultConfiguration);
|
||||||
|
|
||||||
// normal clients
|
// normal clients
|
||||||
services.AddHttpClient<HutaoClient>(DefaultConfiguration);
|
|
||||||
services.AddHttpClient<AnnouncementClient>(DefaultConfiguration);
|
services.AddHttpClient<AnnouncementClient>(DefaultConfiguration);
|
||||||
services.AddHttpClient<UserGameRoleClient>(DefaultConfiguration);
|
|
||||||
services.AddHttpClient<EnkaClient>(DefaultConfiguration);
|
services.AddHttpClient<EnkaClient>(DefaultConfiguration);
|
||||||
|
services.AddHttpClient<HutaoClient>(DefaultConfiguration);
|
||||||
|
services.AddHttpClient<UserGameRoleClient>(DefaultConfiguration);
|
||||||
|
|
||||||
// x-rpc clients
|
// x-rpc clients
|
||||||
services.AddHttpClient<GameRecordClient>(XRpcConfiguration);
|
services.AddHttpClient<GameRecordClient>(XRpcConfiguration);
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色名片转换器
|
||||||
|
/// </summary>
|
||||||
|
internal class AvatarNameCardPicConverter : IValueConverter
|
||||||
|
{
|
||||||
|
private const string BaseUrl = "https://static.snapgenshin.com/NameCardPic/UI_NameCardPic_{0}_P.png";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public object Convert(object value, Type targetType, object parameter, string language)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Avatar.Avatar avatar = (Avatar.Avatar)value;
|
||||||
|
string avatarName = avatar.Icon[14..];
|
||||||
|
return new Uri(string.Format(BaseUrl, avatarName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||||
|
{
|
||||||
|
throw Must.NeverHappen();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||||
Publisher="CN=DGP Studio"
|
Publisher="CN=DGP Studio"
|
||||||
Version="1.0.11.0" />
|
Version="1.0.13.0" />
|
||||||
|
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>胡桃</DisplayName>
|
<DisplayName>胡桃</DisplayName>
|
||||||
|
|||||||
@@ -71,10 +71,9 @@
|
|||||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
|
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Media" Version="7.1.2" />
|
<PackageReference Include="CommunityToolkit.WinUI.UI.Media" Version="7.1.2" />
|
||||||
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="4.5.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.7" />
|
||||||
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.5.1" />
|
<!-- The PrivateAssets & IncludeAssets of Microsoft.EntityFrameworkCore.Tools should be remove to prevent multiple deps file-->
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.6" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
|
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
xmlns:cwum="using:CommunityToolkit.WinUI.UI.Media"
|
|
||||||
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
|
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||||
xmlns:shc="using:Snap.Hutao.Control"
|
xmlns:shc="using:Snap.Hutao.Control"
|
||||||
@@ -27,6 +26,7 @@
|
|||||||
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
|
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
|
||||||
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
|
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
|
||||||
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
|
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
|
||||||
|
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
|
||||||
|
|
||||||
<shmmc:FightPropertyConverter x:Key="FightPropertyConverter"/>
|
<shmmc:FightPropertyConverter x:Key="FightPropertyConverter"/>
|
||||||
<shmmc:FightPropertyValueFormatter x:Key="FightPropertyValueFormatter"/>
|
<shmmc:FightPropertyValueFormatter x:Key="FightPropertyValueFormatter"/>
|
||||||
@@ -145,43 +145,47 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
|
|
||||||
<Grid>
|
<SplitView
|
||||||
<SplitView
|
IsPaneOpen="True"
|
||||||
IsPaneOpen="True"
|
DisplayMode="Inline"
|
||||||
DisplayMode="Inline"
|
OpenPaneLength="200">
|
||||||
OpenPaneLength="200">
|
<SplitView.PaneBackground>
|
||||||
<SplitView.PaneBackground>
|
<SolidColorBrush Color="{StaticResource CardBackgroundFillColorSecondary}"/>
|
||||||
<SolidColorBrush Color="{StaticResource CardBackgroundFillColorSecondary}"/>
|
</SplitView.PaneBackground>
|
||||||
</SplitView.PaneBackground>
|
<SplitView.Pane>
|
||||||
<SplitView.Pane>
|
<ListView
|
||||||
<ListView
|
SelectionMode="Single"
|
||||||
SelectionMode="Single"
|
ItemsSource="{Binding Avatars}"
|
||||||
ItemsSource="{Binding Avatars}"
|
SelectedItem="{Binding Selected,Mode=TwoWay}">
|
||||||
SelectedItem="{Binding Selected,Mode=TwoWay}">
|
<ListView.ItemTemplate>
|
||||||
<ListView.ItemTemplate>
|
<DataTemplate>
|
||||||
<DataTemplate>
|
<Grid>
|
||||||
<Grid>
|
<Grid.ColumnDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<ColumnDefinition Width="auto"/>
|
||||||
<ColumnDefinition Width="auto"/>
|
<ColumnDefinition/>
|
||||||
<ColumnDefinition/>
|
</Grid.ColumnDefinitions>
|
||||||
</Grid.ColumnDefinitions>
|
<shci:CachedImage
|
||||||
<shci:CachedImage
|
Grid.Column="0"
|
||||||
Grid.Column="0"
|
Width="48"
|
||||||
Width="48"
|
Height="48"
|
||||||
Height="48"
|
Margin="0,0,12,12"
|
||||||
Margin="0,0,12,12"
|
Source="{Binding SideIcon,Converter={StaticResource AvatarSideIconConverter},Mode=OneWay}"/>
|
||||||
Source="{Binding SideIcon,Converter={StaticResource AvatarSideIconConverter},Mode=OneWay}"/>
|
<TextBlock
|
||||||
<TextBlock
|
VerticalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
Grid.Column="1"
|
||||||
Grid.Column="1"
|
Margin="12,0,0,0"
|
||||||
Margin="12,0,0,0"
|
Text="{Binding Name}"/>
|
||||||
Text="{Binding Name}"/>
|
</Grid>
|
||||||
</Grid>
|
</DataTemplate>
|
||||||
</DataTemplate>
|
</ListView.ItemTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView>
|
||||||
</ListView>
|
</SplitView.Pane>
|
||||||
</SplitView.Pane>
|
<SplitView.Content>
|
||||||
<SplitView.Content>
|
<Grid>
|
||||||
|
<shci:Gradient
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Source="{Binding Selected,Converter={StaticResource AvatarNameCardPicConverter}}">
|
||||||
|
</shci:Gradient>
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Margin="0,0,16,16">
|
<StackPanel Margin="0,0,16,16">
|
||||||
<!--简介-->
|
<!--简介-->
|
||||||
@@ -494,7 +498,7 @@
|
|||||||
</Expander>
|
</Expander>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</SplitView.Content>
|
</Grid>
|
||||||
</SplitView>
|
</SplitView.Content>
|
||||||
</Grid>
|
</SplitView>
|
||||||
</Page>
|
</Page>
|
||||||
Reference in New Issue
Block a user