mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
update avatar page style
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Extension;
|
||||
@@ -68,6 +69,7 @@ public partial class App : Application
|
||||
Window = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
Window.Activate();
|
||||
|
||||
logger.LogInformation("Image cache folder : {folder}", Windows.Storage.ApplicationData.Current.TemporaryFolder.Path);
|
||||
if (Ioc.Default.GetRequiredService<IMetadataService>() is IMetadataInitializer initializer)
|
||||
{
|
||||
initializer.InitializeInternalAsync().SafeForget();
|
||||
|
||||
@@ -17,11 +17,6 @@ public class AppDbContextDesignTimeFactory : IDesignTimeDbContextFactory<AppDbCo
|
||||
public AppDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
MyDocumentContext myDocument = new(new());
|
||||
myDocument.EnsureDirectory();
|
||||
|
||||
string dbFile = myDocument.Locate("Userdata.db");
|
||||
string sqlConnectionString = $"Data Source={dbFile}";
|
||||
|
||||
return AppDbContext.Create(sqlConnectionString);
|
||||
return AppDbContext.Create($"Data Source={myDocument.Locate("Userdata.db")}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ internal abstract class FileSystemContext
|
||||
public FileSystemContext(IFileSystemLocation location)
|
||||
{
|
||||
this.location = location;
|
||||
EnsureDirectory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,22 +63,6 @@ internal abstract class FileSystemContext
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查根目录
|
||||
/// </summary>
|
||||
/// <returns>是否创建了路径</returns>
|
||||
public bool EnsureDirectory()
|
||||
{
|
||||
string folder = location.GetPath();
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查文件是否存在
|
||||
/// </summary>
|
||||
@@ -170,4 +155,20 @@ internal abstract class FileSystemContext
|
||||
{
|
||||
return File.Create(Locate(file));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查根目录
|
||||
/// </summary>
|
||||
/// <returns>是否创建了路径</returns>
|
||||
private bool EnsureDirectory()
|
||||
{
|
||||
string folder = location.GetPath();
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.HostBackdrop;
|
||||
|
||||
/// <summary>
|
||||
/// 回退行为
|
||||
/// </summary>
|
||||
public enum BackbdropFallBackBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// 回退到无
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// 回退到亚克力
|
||||
/// </summary>
|
||||
Acrylic,
|
||||
}
|
||||
@@ -16,10 +16,9 @@ namespace Snap.Hutao.Control.HostBackdrop;
|
||||
public class SystemBackdrop
|
||||
{
|
||||
private readonly Window window;
|
||||
private readonly BackbdropFallBackBehavior fallBackBehavior;
|
||||
|
||||
private WindowsSystemDispatcherQueueHelper? dispatcherQueueHelper;
|
||||
private ISystemBackdropControllerWithTargets? backdropController;
|
||||
private MicaController? backdropController;
|
||||
private SystemBackdropConfiguration? configurationSource;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,16 +26,14 @@ public class SystemBackdrop
|
||||
/// </summary>
|
||||
/// <param name="window">窗体</param>
|
||||
/// <param name="fallBackBehavior">回退行为</param>
|
||||
public SystemBackdrop(Window window, BackbdropFallBackBehavior fallBackBehavior = BackbdropFallBackBehavior.Acrylic)
|
||||
public SystemBackdrop(Window window)
|
||||
{
|
||||
this.window = window;
|
||||
this.fallBackBehavior = fallBackBehavior;
|
||||
}
|
||||
|
||||
private enum BackDropType
|
||||
{
|
||||
None,
|
||||
Acrylic,
|
||||
Mica,
|
||||
}
|
||||
|
||||
@@ -46,9 +43,7 @@ public class SystemBackdrop
|
||||
/// <returns>是否设置成功</returns>
|
||||
public bool TrySetBackdrop()
|
||||
{
|
||||
BackDropType targetBackDropType = ResolveBackdropType();
|
||||
|
||||
if (targetBackDropType == BackDropType.None)
|
||||
if (!MicaController.IsSupported())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -67,12 +62,7 @@ public class SystemBackdrop
|
||||
configurationSource.IsInputActive = true;
|
||||
SetConfigurationSourceTheme();
|
||||
|
||||
backdropController = targetBackDropType switch
|
||||
{
|
||||
BackDropType.Mica => new MicaController(),
|
||||
BackDropType.Acrylic => new DesktopAcrylicController(),
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
backdropController = new MicaController();
|
||||
|
||||
ICompositionSupportsSystemBackdrop target = window.As<ICompositionSupportsSystemBackdrop>();
|
||||
backdropController.AddSystemBackdropTarget(target);
|
||||
@@ -82,27 +72,6 @@ public class SystemBackdrop
|
||||
}
|
||||
}
|
||||
|
||||
private BackDropType ResolveBackdropType()
|
||||
{
|
||||
BackDropType targetBackDropType = BackDropType.None;
|
||||
if (MicaController.IsSupported())
|
||||
{
|
||||
targetBackDropType = BackDropType.Mica;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fallBackBehavior == BackbdropFallBackBehavior.Acrylic)
|
||||
{
|
||||
if (DesktopAcrylicController.IsSupported())
|
||||
{
|
||||
targetBackDropType = BackDropType.Acrylic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetBackDropType;
|
||||
}
|
||||
|
||||
private void WindowActivated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
Must.NotNull(configurationSource!);
|
||||
|
||||
@@ -11,14 +11,14 @@ namespace Snap.Hutao.Control;
|
||||
/// </summary>
|
||||
public class BindingProxy : DependencyObject
|
||||
{
|
||||
private static readonly DependencyProperty DataProperty = Property<BindingProxy>.Depend<object>(nameof(DataContext));
|
||||
private static readonly DependencyProperty DataContextProperty = Property<BindingProxy>.Depend<object>(nameof(DataContext));
|
||||
|
||||
/// <summary>
|
||||
/// 数据上下文
|
||||
/// </summary>
|
||||
public object? DataContext
|
||||
{
|
||||
get => GetValue(DataProperty);
|
||||
set => SetValue(DataProperty, value);
|
||||
get => GetValue(DataContextProperty);
|
||||
set => SetValue(DataContextProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,16 +32,21 @@ public class CachedImage : ImageEx
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<ImageSource> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
||||
{
|
||||
BitmapImage image;
|
||||
BitmapImage? image;
|
||||
try
|
||||
{
|
||||
image = await ImageCache.Instance.GetFromCacheAsync(imageUri, true, token);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// task was explicitly canceled
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// maybe the image is corrupted remove it and re-download
|
||||
// maybe the image is corrupted, remove it.
|
||||
await ImageCache.Instance.RemoveAsync(imageUri.Enumerate());
|
||||
image = await ImageCache.Instance.GetFromCacheAsync(imageUri, false, token);
|
||||
throw;
|
||||
}
|
||||
|
||||
// check token state to determine whether the operation should be canceled.
|
||||
|
||||
101
src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs
Normal file
101
src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// some part of this file came from:
|
||||
// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Core;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control.Text;
|
||||
|
||||
/// <summary>
|
||||
/// 专用于呈现描述文本的文本块
|
||||
/// </summary>
|
||||
public class DescriptionTextBlock : ContentControl
|
||||
{
|
||||
private static readonly DependencyProperty DescriptionProperty =
|
||||
Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的呈现描述文本的文本块
|
||||
/// </summary>
|
||||
public DescriptionTextBlock()
|
||||
{
|
||||
Content = new TextBlock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可绑定的描述文本
|
||||
/// </summary>
|
||||
public string Description
|
||||
{
|
||||
get => (string)GetValue(DescriptionProperty);
|
||||
set => SetValue(DescriptionProperty, value);
|
||||
}
|
||||
|
||||
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
TextBlock text = (TextBlock)((DescriptionTextBlock)d).Content;
|
||||
text.Inlines.Clear();
|
||||
|
||||
string[] lines = ((string)e.NewValue).Split('\n');
|
||||
foreach (string line in lines)
|
||||
{
|
||||
MatchCollection matches = Regex.Matches(line, @"<color=([^>]+)>([^<]+)</color>");
|
||||
string left, right = line;
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
string matched = match.Groups[0].Value;
|
||||
int matchPosition = right.IndexOf(matched);
|
||||
left = right[..matchPosition];
|
||||
right = right[(matchPosition + matched.Length)..];
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(left))
|
||||
{
|
||||
text.Inlines.Add(new Run { Text = left });
|
||||
}
|
||||
|
||||
string hexColor = match.Groups[1].Value;
|
||||
string content = match.Groups[2].Value;
|
||||
|
||||
text.Inlines.Add(new Run { Text = content, Foreground = GetSolidColorBrush(hexColor[..7]) });
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(right))
|
||||
{
|
||||
if (right.Contains("<i>"))
|
||||
{
|
||||
string italic = right.Replace("<i>", string.Empty).Replace("</i>", string.Empty);
|
||||
text.Inlines.Add(new Run { Text = italic, FontStyle = Windows.UI.Text.FontStyle.Italic });
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Inlines.Add(new Run { Text = right });
|
||||
}
|
||||
}
|
||||
|
||||
text.Inlines.Add(new LineBreak());
|
||||
}
|
||||
|
||||
if (text.Inlines.LastOrDefault() is LineBreak newline)
|
||||
{
|
||||
text.Inlines.Remove(newline);
|
||||
}
|
||||
}
|
||||
|
||||
private static SolidColorBrush GetSolidColorBrush(string hex)
|
||||
{
|
||||
hex = hex.Replace("#", string.Empty);
|
||||
byte r = (byte)Convert.ToUInt32(hex.Substring(0, 2), 16);
|
||||
byte g = (byte)Convert.ToUInt32(hex.Substring(2, 2), 16);
|
||||
byte b = (byte)Convert.ToUInt32(hex.Substring(4, 2), 16);
|
||||
return new SolidColorBrush(Color.FromArgb(255, r, g, b));
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Extension;
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将源转换为仅包含单个元素的集合
|
||||
/// 将源转换为仅包含单个元素的枚举
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源的类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
@@ -79,51 +79,6 @@ public static class EnumerableExtensions
|
||||
return source.FirstOrDefault(predicate) ?? source.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将二维可枚举对象一维化
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>扁平的对象</returns>
|
||||
public static IEnumerable<TSource> Flatten<TSource>(this IEnumerable<IEnumerable<TSource>> source)
|
||||
{
|
||||
return source.SelectMany(x => x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对集合中的每个物品执行指定的操作
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">集合类型</typeparam>
|
||||
/// <param name="source">集合</param>
|
||||
/// <param name="action">指定的操作</param>
|
||||
/// <returns>修改后的集合</returns>
|
||||
public static IEnumerable<TSource> ForEach<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
|
||||
{
|
||||
foreach (TSource item in source)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对集合中的每个物品执行指定的操作
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">集合类型</typeparam>
|
||||
/// <param name="source">集合</param>
|
||||
/// <param name="func">指定的操作</param>
|
||||
/// <returns>修改后的集合</returns>
|
||||
public static async Task<IEnumerable<TSource>> ForEachAsync<TSource>(this IEnumerable<TSource> source, Func<TSource, Task> func)
|
||||
{
|
||||
foreach (TSource item in source)
|
||||
{
|
||||
await func(item);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个对 <see cref="TItem"/> 类型的计数器
|
||||
/// </summary>
|
||||
|
||||
@@ -8,7 +8,7 @@ using WinRT.Interop;
|
||||
namespace Snap.Hutao.Factory;
|
||||
|
||||
/// <inheritdoc cref="IPickerFactory"/>
|
||||
[Injection(InjectAs.Transient)]
|
||||
[Injection(InjectAs.Transient, typeof(IPickerFactory))]
|
||||
internal class PickerFactory : IPickerFactory
|
||||
{
|
||||
private readonly MainWindow mainWindow;
|
||||
|
||||
@@ -42,7 +42,6 @@ internal static class IocConfiguration
|
||||
public static IServiceCollection AddDatebase(this IServiceCollection services)
|
||||
{
|
||||
MyDocumentContext myDocument = new(new());
|
||||
myDocument.EnsureDirectory();
|
||||
|
||||
string dbFile = myDocument.Locate("Userdata.db");
|
||||
string sqlConnectionString = $"Data Source={dbFile}";
|
||||
|
||||
@@ -66,12 +66,6 @@ public class User : Observable
|
||||
private set => Set(ref selectedUserGameRole, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除命令
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public ICommand? RemoveCommand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 复制Cookie命令
|
||||
/// </summary>
|
||||
@@ -126,7 +120,6 @@ public class User : Observable
|
||||
}
|
||||
|
||||
CopyCookieCommand = new RelayCommand(CopyCookie);
|
||||
Must.NotNull(RemoveCommand!);
|
||||
|
||||
UserInfo = await userClient
|
||||
.GetUserFullInfoAsync(this, token)
|
||||
|
||||
@@ -18,5 +18,5 @@ public class DescParam
|
||||
/// <summary>
|
||||
/// 参数
|
||||
/// </summary>
|
||||
public IEnumerable<LevelParam<int>> Parameters { get; set; } = default!;
|
||||
public IEnumerable<LevelParam<int, double>> Parameters { get; set; } = default!;
|
||||
}
|
||||
@@ -40,26 +40,27 @@ public class FetterInfo
|
||||
/// </summary>
|
||||
public int BirthDay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的生日日期
|
||||
/// </summary>
|
||||
public string BirthFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"{BirthMonth} 月 {BirthDay} 日";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 神之眼属性-前
|
||||
/// </summary>
|
||||
public string VisionBefore { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 神之眼属性-后
|
||||
/// </summary>
|
||||
public string VisionAfter { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 命座-前
|
||||
/// </summary>
|
||||
public string ConstellationBefore { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 命座-后
|
||||
/// </summary>
|
||||
public string ConstellationAfter { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 中文CV
|
||||
/// </summary>
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
/// <summary>
|
||||
/// 角色头像转换器
|
||||
/// </summary>
|
||||
internal class IconConverter : IValueConverter
|
||||
internal class AvatarIconConverter : IValueConverter
|
||||
{
|
||||
private const string BaseUrl = "https://upload-bbs.mihoyo.com/game_record/genshin/character_icon/{0}.png";
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
/// <summary>
|
||||
/// 角色侧面头像转换器
|
||||
/// </summary>
|
||||
internal class SideIconConverter : IValueConverter
|
||||
internal class AvatarSideIconConverter : IValueConverter
|
||||
{
|
||||
private const string BaseUrl = "https://upload-bbs.mihoyo.com/game_record/genshin/character_side_icon/{0}.png";
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 描述参数解析器
|
||||
/// </summary>
|
||||
internal class DescParamDescriptor : IValueConverter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
DescParam descParam = (DescParam)value;
|
||||
IEnumerable<DescFormat> parsedDescriptions = descParam.Descriptions.Select(desc =>
|
||||
{
|
||||
string[] parts = desc.Split('|', 2);
|
||||
return new DescFormat(parts[0], parts[1]);
|
||||
});
|
||||
|
||||
IList<IList<string>> parameters = descParam.Parameters
|
||||
.Select(param =>
|
||||
{
|
||||
IList<string> parameters = GetFormattedParameters(parsedDescriptions, param.Parameters);
|
||||
parameters.Insert(0, param.Level.ToString());
|
||||
return parameters;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
List<string> descList = parsedDescriptions.Select(p => p.Description).ToList();
|
||||
descList.Insert(0, "等级");
|
||||
return new DescParamInternal(descList, parameters);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
|
||||
private IList<string> GetFormattedParameters(IEnumerable<DescFormat> formats, IList<double> param)
|
||||
{
|
||||
List<string> results = new();
|
||||
foreach (DescFormat descFormat in formats)
|
||||
{
|
||||
string format = descFormat.Format;
|
||||
string resultFormatted = Regex.Replace(format, @"{param\d+.*?}", match => EvaluateMatch(match, param));
|
||||
results.Add(resultFormatted);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private string EvaluateMatch(Match match, IList<double> param)
|
||||
{
|
||||
if (match.Success)
|
||||
{
|
||||
string[] parts = match.Value[1..^1].Split(':', 2);
|
||||
|
||||
int index = int.Parse(parts[0][5..]) - 1;
|
||||
if (parts[1] == "I")
|
||||
{
|
||||
return ((int)param[index]).ToString();
|
||||
}
|
||||
|
||||
if (parts[1] == "F1P")
|
||||
{
|
||||
return string.Format("{0:P1}", param[index]);
|
||||
}
|
||||
|
||||
if (parts[1] == "F2P")
|
||||
{
|
||||
return string.Format("{0:P2}", param[index]);
|
||||
}
|
||||
|
||||
return string.Format($"{{0:{parts[1]}}}", param[index]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private class DescFormat
|
||||
{
|
||||
public DescFormat(string description, string format)
|
||||
{
|
||||
Description = description;
|
||||
Format = format;
|
||||
}
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Format { get; set; }
|
||||
}
|
||||
|
||||
private class DescParamInternal
|
||||
{
|
||||
public DescParamInternal(IList<string> descriptions, IList<IList<string>> parameters)
|
||||
{
|
||||
Descriptions = descriptions;
|
||||
Parameters = parameters;
|
||||
}
|
||||
|
||||
public IList<string> Descriptions { get; set; }
|
||||
|
||||
public IList<IList<string>> Parameters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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 ElementNameIconConverter : IValueConverter
|
||||
{
|
||||
private const string BaseUrl = "https://static.snapgenshin.com/IconElement/UI_Icon_Element_{0}.png";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
string element = (string)value switch
|
||||
{
|
||||
"雷" => "Electric",
|
||||
"火" => "Fire",
|
||||
"草" => "Grass",
|
||||
"冰" => "Ice",
|
||||
"岩" => "Rock",
|
||||
"水" => "Water",
|
||||
"风" => "Wind",
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
|
||||
return new Uri(string.Format(BaseUrl, element));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,4 @@ internal class QualityConverter : IValueConverter
|
||||
{
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 元素名称图标转换器
|
||||
/// </summary>
|
||||
internal class WeaponTypeIconConverter : IValueConverter
|
||||
{
|
||||
private const string BaseUrl = "https://static.snapgenshin.com/Skill/Skill_A_{0}.png";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
string element = (WeaponType)value switch
|
||||
{
|
||||
WeaponType.WEAPON_SWORD_ONE_HAND => "01",
|
||||
WeaponType.WEAPON_BOW => "02",
|
||||
WeaponType.WEAPON_POLE => "03",
|
||||
WeaponType.WEAPON_CLAYMORE => "04",
|
||||
WeaponType.WEAPON_CATALYST => "Catalyst_MD",
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
|
||||
return new Uri(string.Format(BaseUrl, element));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ namespace Snap.Hutao.Model.Metadata;
|
||||
/// 等级与参数
|
||||
/// </summary>
|
||||
/// <typeparam name="TLevel">等级的类型</typeparam>
|
||||
public class LevelParam<TLevel>
|
||||
/// <typeparam name="TParam">参数的类型</typeparam>
|
||||
public class LevelParam<TLevel, TParam>
|
||||
{
|
||||
/// <summary>
|
||||
/// 等级
|
||||
@@ -19,5 +20,5 @@ public class LevelParam<TLevel>
|
||||
/// <summary>
|
||||
/// 参数
|
||||
/// </summary>
|
||||
public IList<double> Parameters { get; set; } = default!;
|
||||
public IList<TParam> Parameters { get; set; } = default!;
|
||||
}
|
||||
@@ -19,5 +19,5 @@ public class PropertyInfo
|
||||
/// <summary>
|
||||
/// 参数
|
||||
/// </summary>
|
||||
public IEnumerable<LevelParam<string>> Parameters { get; set; } = default!;
|
||||
public IEnumerable<LevelParam<string, double>> Parameters { get; set; } = default!;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity
|
||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||
Publisher="CN=DGP Studio"
|
||||
Version="1.0.10.0" />
|
||||
Version="1.0.11.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>胡桃</DisplayName>
|
||||
|
||||
@@ -24,7 +24,7 @@ public static class Program
|
||||
|
||||
Application.Start(p =>
|
||||
{
|
||||
DispatcherQueueSynchronizationContext context = new(DispatcherQueue.GetForCurrentThread());
|
||||
SynchronizationContext context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
_ = new App();
|
||||
});
|
||||
|
||||
@@ -69,11 +69,4 @@ internal interface IMetadataService
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>武器列表</returns>
|
||||
ValueTask<IEnumerable<Weapon>> GetWeaponsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步更新元数据
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>更新是否完成</returns>
|
||||
Task<bool> UpdateMetadataAsync(CancellationToken token = default);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
{
|
||||
private const string MetaAPIHost = "http://hutao-metadata.snapgenshin.com";
|
||||
private const string MetaFileName = "Meta.json";
|
||||
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly FileSystemContext metadataContext;
|
||||
@@ -78,40 +79,14 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
public async Task InitializeInternalAsync(CancellationToken token = default)
|
||||
{
|
||||
logger.LogInformation("元数据初始化开始");
|
||||
metadataContext.EnsureDirectory();
|
||||
|
||||
IsInitialized = await UpdateMetadataAsync(token)
|
||||
IsInitialized = await TryUpdateMetadataAsync(token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
initializeCompletionSource.SetResult();
|
||||
logger.LogInformation("元数据初始化完成");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> UpdateMetadataAsync(CancellationToken token = default)
|
||||
{
|
||||
IDictionary<string, string>? metaMd5Map = await httpClient
|
||||
.GetFromJsonAsync<IDictionary<string, string>>($"{MetaAPIHost}/{MetaFileName}", options, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (metaMd5Map is null)
|
||||
{
|
||||
infoBarService.Error("元数据校验文件解析失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
await CheckMetadataAsync(metaMd5Map, token).ConfigureAwait(false);
|
||||
|
||||
using (FileStream metaFileStream = metadataContext.Create(MetaFileName))
|
||||
{
|
||||
await JsonSerializer
|
||||
.SerializeAsync(metaFileStream, metaMd5Map, options, token)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<IEnumerable<AchievementGoal>> GetAchievementGoalsAsync(CancellationToken token = default)
|
||||
{
|
||||
@@ -154,34 +129,30 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
return GetMetadataAsync<IEnumerable<Weapon>>("Weapon", token);
|
||||
}
|
||||
|
||||
private async ValueTask<T> GetMetadataAsync<T>(string fileName, CancellationToken token)
|
||||
where T : class
|
||||
private async Task<bool> TryUpdateMetadataAsync(CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "元数据服务尚未初始化,或初始化失败");
|
||||
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}";
|
||||
|
||||
if (memoryCache.TryGetValue(cacheKey, out object? value))
|
||||
{
|
||||
return Must.NotNull((value as T)!);
|
||||
}
|
||||
|
||||
T? result = await JsonSerializer
|
||||
.DeserializeAsync<T>(metadataContext.OpenRead($"{fileName}.json"), options, token)
|
||||
// download meta check file
|
||||
IDictionary<string, string>? metaMd5Map = await httpClient
|
||||
.GetFromJsonAsync<IDictionary<string, string>>($"{MetaAPIHost}/{MetaFileName}", options, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return memoryCache.Set(cacheKey, Must.NotNull(result!));
|
||||
}
|
||||
|
||||
private async Task<string> GetFileMd5Async(string fileFullName, CancellationToken token)
|
||||
{
|
||||
using (FileStream stream = metadataContext.OpenRead(fileFullName))
|
||||
if (metaMd5Map is null)
|
||||
{
|
||||
byte[] bytes = await MD5.Create()
|
||||
.ComputeHashAsync(stream, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Convert.ToHexString(bytes);
|
||||
infoBarService.Error("元数据校验文件解析失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
await CheckMetadataAsync(metaMd5Map, token).ConfigureAwait(false);
|
||||
|
||||
// save metadataFile
|
||||
using (FileStream metaFileStream = metadataContext.Create(MetaFileName))
|
||||
{
|
||||
await JsonSerializer
|
||||
.SerializeAsync(metaFileStream, metaMd5Map, options, token)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -193,6 +164,7 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
/// <returns>令牌</returns>
|
||||
private async Task CheckMetadataAsync(IDictionary<string, string> metaMd5Map, CancellationToken token)
|
||||
{
|
||||
// TODO: Make this foreach async to imporve speed
|
||||
// enumerate files and compare md5
|
||||
foreach ((string fileName, string md5) in metaMd5Map)
|
||||
{
|
||||
@@ -215,6 +187,18 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetFileMd5Async(string fileFullName, CancellationToken token)
|
||||
{
|
||||
using (FileStream stream = metadataContext.OpenRead(fileFullName))
|
||||
{
|
||||
byte[] bytes = await MD5.Create()
|
||||
.ComputeHashAsync(stream, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Convert.ToHexString(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadMetadataAsync(string fileFullName, CancellationToken token)
|
||||
{
|
||||
Stream sourceStream = await httpClient
|
||||
@@ -238,4 +222,22 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
|
||||
logger.LogInformation("{file} 下载完成", fileFullName);
|
||||
}
|
||||
|
||||
private async ValueTask<T> GetMetadataAsync<T>(string fileName, CancellationToken token)
|
||||
where T : class
|
||||
{
|
||||
Verify.Operation(IsInitialized, "元数据服务尚未初始化,或初始化失败");
|
||||
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}";
|
||||
|
||||
if (memoryCache.TryGetValue(cacheKey, out object? value))
|
||||
{
|
||||
return Must.NotNull((value as T)!);
|
||||
}
|
||||
|
||||
T? result = await JsonSerializer
|
||||
.DeserializeAsync<T>(metadataContext.OpenRead($"{fileName}.json"), options, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return memoryCache.Set(cacheKey, Must.NotNull(result!));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Service.Navigation;
|
||||
/// <summary>
|
||||
/// 为 <see cref="NavigationExtra"/> 提供抽象接口
|
||||
/// </summary>
|
||||
public interface INavigationExtra
|
||||
public interface INavigationData
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据
|
||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Service.Navigation;
|
||||
/// <summary>
|
||||
/// 导航额外信息
|
||||
/// </summary>
|
||||
public class NavigationExtra : INavigationExtra, INavigationAwaiter
|
||||
public class NavigationExtra : INavigationData, INavigationAwaiter
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务完成源
|
||||
|
||||
@@ -7,6 +7,7 @@ using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.View.Helper;
|
||||
using Snap.Hutao.View.Page;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.Service.Navigation;
|
||||
@@ -85,8 +86,7 @@ internal class NavigationService : INavigationService
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationViewItem? target = NavigationView.MenuItems
|
||||
.OfType<NavigationViewItem>()
|
||||
NavigationViewItem? target = EnumerateMenuItems(NavigationView.MenuItems)
|
||||
.SingleOrDefault(menuItem => NavHelper.GetNavigateTo(menuItem) == pageType);
|
||||
|
||||
NavigationView.SelectedItem = target;
|
||||
@@ -163,6 +163,24 @@ internal class NavigationService : INavigationService
|
||||
NavigationView.IsPaneOpen = LocalSetting.GetValueType(SettingKeys.IsNavPaneOpen, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 遍历所有子菜单项
|
||||
/// </summary>
|
||||
/// <param name="items">项列表</param>
|
||||
/// <returns>枚举器</returns>
|
||||
private IEnumerable<NavigationViewItem> EnumerateMenuItems(IList<object> items)
|
||||
{
|
||||
foreach (NavigationViewItem item in items.OfType<NavigationViewItem>())
|
||||
{
|
||||
yield return item;
|
||||
|
||||
foreach (NavigationViewItem subItem in EnumerateMenuItems(item.MenuItems))
|
||||
{
|
||||
yield return subItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
|
||||
{
|
||||
Selected = NavigationView?.SelectedItem as NavigationViewItem;
|
||||
@@ -170,8 +188,12 @@ internal class NavigationService : INavigationService
|
||||
? typeof(SettingPage)
|
||||
: NavHelper.GetNavigateTo(Selected);
|
||||
|
||||
INavigationAwaiter navigationAwaiter = new NavigationExtra(NavHelper.GetExtraData(Selected));
|
||||
Navigate(Must.NotNull(targetType!), navigationAwaiter, false);
|
||||
// ignore item that doesn't have nav type specified
|
||||
if (targetType != null)
|
||||
{
|
||||
INavigationAwaiter navigationAwaiter = new NavigationExtra(NavHelper.GetExtraData(Selected));
|
||||
Navigate(targetType, navigationAwaiter, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
|
||||
|
||||
@@ -24,7 +24,7 @@ public interface IUserService
|
||||
/// </summary>
|
||||
/// <param name="removeCommand">移除用户命令</param>
|
||||
/// <returns>准备完成的用户信息枚举</returns>
|
||||
Task<ObservableCollection<User>> GetInitializedUsersAsync(ICommand removeCommand);
|
||||
Task<ObservableCollection<User>> GetInitializedUsersAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步添加用户
|
||||
|
||||
@@ -121,7 +121,7 @@ internal class UserService : IUserService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ObservableCollection<User>> GetInitializedUsersAsync(ICommand removeCommand)
|
||||
public async Task<ObservableCollection<User>> GetInitializedUsersAsync()
|
||||
{
|
||||
if (cachedUsers == null)
|
||||
{
|
||||
@@ -133,7 +133,6 @@ internal class UserService : IUserService
|
||||
|
||||
foreach (User user in cachedUsers)
|
||||
{
|
||||
user.RemoveCommand = removeCommand;
|
||||
await user
|
||||
.InitializeAsync(userClient, userGameRoleClient)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
<None Remove="View\Page\AnnouncementContentPage.xaml" />
|
||||
<None Remove="View\Page\AnnouncementPage.xaml" />
|
||||
<None Remove="View\Page\SettingPage.xaml" />
|
||||
<None Remove="View\Page\WelcomePage.xaml" />
|
||||
<None Remove="View\Page\WikiAvatarPage.xaml" />
|
||||
<None Remove="View\TitleView.xaml" />
|
||||
<None Remove="View\UserView.xaml" />
|
||||
@@ -154,11 +153,6 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Page\WelcomePage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\UserDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
||||
mc:Ignorable="d"
|
||||
Width="64"
|
||||
Height="64">
|
||||
Width="80"
|
||||
Height="80">
|
||||
<UserControl.Resources>
|
||||
<shmmc:IconConverter x:Key="IconConverter"/>
|
||||
<shmmc:AvatarIconConverter x:Key="IconConverter"/>
|
||||
<shmmc:QualityConverter x:Key="QualityConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Snap.Hutao.View.Helper;
|
||||
public sealed class NavHelper
|
||||
{
|
||||
private static readonly DependencyProperty NavigateToProperty = Property<NavHelper>.Attach<Type>("NavigateTo");
|
||||
private static readonly DependencyProperty ExtraDataProperty = Property<NavHelper>.Attach<INavigationExtra>("ExtraData");
|
||||
private static readonly DependencyProperty ExtraDataProperty = Property<NavHelper>.Attach<INavigationData>("ExtraData");
|
||||
|
||||
/// <summary>
|
||||
/// 获取导航项的目标页面类型
|
||||
@@ -41,9 +41,9 @@ public sealed class NavHelper
|
||||
/// </summary>
|
||||
/// <param name="item">待获取的导航项</param>
|
||||
/// <returns>目标页面类型的额外数据</returns>
|
||||
public static INavigationExtra? GetExtraData(NavigationViewItem? item)
|
||||
public static INavigationData? GetExtraData(NavigationViewItem? item)
|
||||
{
|
||||
return item?.GetValue(ExtraDataProperty) as INavigationExtra;
|
||||
return item?.GetValue(ExtraDataProperty) as INavigationData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,7 +51,7 @@ public sealed class NavHelper
|
||||
/// </summary>
|
||||
/// <param name="item">待设置的导航项</param>
|
||||
/// <param name="value">新的目标页面类型</param>
|
||||
public static void SetExtraData(NavigationViewItem item, INavigationExtra value)
|
||||
public static void SetExtraData(NavigationViewItem item, INavigationData value)
|
||||
{
|
||||
item.SetValue(ExtraDataProperty, value);
|
||||
}
|
||||
|
||||
@@ -12,13 +12,12 @@
|
||||
<Thickness x:Key="NavigationViewContentMargin">0,48,0,0</Thickness>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<!-- x:Bind can't get property update here seems like a WinUI 3 bug-->
|
||||
<NavigationView
|
||||
x:Name="NavView"
|
||||
CompactPaneLength="48"
|
||||
OpenPaneLength="244"
|
||||
CompactModeThresholdWidth="128"
|
||||
ExpandedModeThresholdWidth="720"
|
||||
CompactModeThresholdWidth="16"
|
||||
ExpandedModeThresholdWidth="16"
|
||||
IsPaneOpen="True"
|
||||
IsBackEnabled="{Binding ElementName=ContentFrame,Path=CanGoBack}">
|
||||
|
||||
@@ -26,7 +25,8 @@
|
||||
|
||||
<NavigationViewItem Content="活动" helper:NavHelper.NavigateTo="page:AnnouncementPage">
|
||||
<NavigationViewItem.Icon>
|
||||
<BitmapIcon UriSource="ms-appx:///Resource/Icon/UI_BtnIcon_ActivityEntry.png"/>
|
||||
<BitmapIcon
|
||||
UriSource="ms-appx:///Resource/Icon/UI_BtnIcon_ActivityEntry.png"/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Snap.Hutao.Control.Cancellable;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.ViewModel;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
@@ -19,4 +21,15 @@ public sealed partial class AchievementPage : CancellablePage
|
||||
InitializeWith<AchievementViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if (e.Parameter is INavigationData extra)
|
||||
{
|
||||
extra.NotifyNavigationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +36,14 @@ openInWebview: function(url){ location.href = url }}";
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if (e.Parameter is INavigationExtra extra)
|
||||
if (e.Parameter is INavigationData extra)
|
||||
{
|
||||
targetContent = extra.Data as string;
|
||||
LoadAnnouncementAsync(extra).SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadAnnouncementAsync(INavigationExtra extra)
|
||||
private async Task LoadAnnouncementAsync(INavigationData extra)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -27,9 +27,7 @@
|
||||
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
|
||||
</shcc:CancellablePage.Resources>
|
||||
<Grid>
|
||||
<ScrollViewer
|
||||
Padding="0,0,4,0"
|
||||
Visibility="{Binding OpeningUI.IsWorking,Converter={StaticResource BoolToVisibilityRevertConverter}}">
|
||||
<ScrollViewer Padding="0,0,4,0">
|
||||
<ItemsControl
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding Announcement.List}"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Snap.Hutao.Control.Cancellable;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.ViewModel;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
@@ -19,4 +21,15 @@ public sealed partial class AnnouncementPage : CancellablePage
|
||||
InitializeWith<AnnouncementViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if (e.Parameter is INavigationData extra)
|
||||
{
|
||||
extra.NotifyNavigationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.ViewModel;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
@@ -18,4 +20,15 @@ public sealed partial class SettingPage : Microsoft.UI.Xaml.Controls.Page
|
||||
DataContext = Ioc.Default.GetRequiredService<SettingViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if (e.Parameter is INavigationData extra)
|
||||
{
|
||||
extra.NotifyNavigationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<Page
|
||||
x:Class="Snap.Hutao.View.Page.WelcomePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Snap.Hutao.View.Page"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid Padding="12">
|
||||
<TextBlock
|
||||
Text="欢迎使用胡桃"
|
||||
Style="{StaticResource TitleTextBlockStyle}"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
|
||||
/// <summary>
|
||||
/// 欢迎页
|
||||
/// </summary>
|
||||
public sealed partial class WelcomePage : Microsoft.UI.Xaml.Controls.Page
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的欢迎页
|
||||
/// </summary>
|
||||
public WelcomePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
xmlns:shct="using:Snap.Hutao.Control.Text"
|
||||
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
@@ -20,42 +22,166 @@
|
||||
</mxic:EventTriggerBehavior>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<Page.Resources>
|
||||
<shmmc:IconConverter x:Key="IconConverter"/>
|
||||
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
|
||||
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
|
||||
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
|
||||
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
|
||||
|
||||
<shmmc:FightPropertyConverter x:Key="FightPropertyConverter"/>
|
||||
<shmmc:FightPropertyValueFormatter x:Key="FightPropertyValueFormatter"/>
|
||||
|
||||
<shmmc:DescParamDescriptor x:Key="DescParamDescriptor"/>
|
||||
|
||||
<shc:BindingProxy
|
||||
x:Key="FightPropertyBindingProxy"
|
||||
DataContext="{Binding Selected.Property.Properties}"/>
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="SystemControlPageBackgroundChromeLowBrush"
|
||||
Color="{StaticResource SystemChromeLowColor}"
|
||||
Opacity="0.2"/>
|
||||
<DataTemplate x:Key="SkillDataTemplate">
|
||||
<Expander
|
||||
Margin="16,16,0,0"
|
||||
Header="{Binding Name}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<shct:DescriptionTextBlock
|
||||
Margin="16,16,16,0"
|
||||
Description="{Binding Description}">
|
||||
<shct:DescriptionTextBlock.Resources>
|
||||
<Style
|
||||
TargetType="TextBlock"
|
||||
BasedOn="{StaticResource BodyTextBlockStyle}">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
</shct:DescriptionTextBlock.Resources>
|
||||
</shct:DescriptionTextBlock>
|
||||
<ScrollViewer
|
||||
Grid.Row="1"
|
||||
VerticalScrollMode="Disabled"
|
||||
HorizontalScrollBarVisibility="Auto">
|
||||
<Grid
|
||||
Margin="16"
|
||||
DataContext="{Binding Proud,Converter={StaticResource DescParamDescriptor}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ItemsControl
|
||||
Grid.Row="0"
|
||||
ItemsSource="{Binding Descriptions}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwuc:UniformGrid
|
||||
ColumnSpacing="16"
|
||||
Columns="{Binding Descriptions.Count}"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
Text="{Binding}"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<ItemsControl
|
||||
Margin="0,16,0,0"
|
||||
Grid.Row="2"
|
||||
ItemsSource="{Binding Parameters}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwuc:UniformGrid
|
||||
ColumnSpacing="16"
|
||||
Columns="{Binding Count}"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
Text="{Binding}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Expander>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="InherentDataTemplate">
|
||||
<Expander
|
||||
Margin="16,16,0,0"
|
||||
Header="{Binding Name}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch">
|
||||
<shct:DescriptionTextBlock
|
||||
Margin="16"
|
||||
Description="{Binding Description}">
|
||||
<shct:DescriptionTextBlock.Resources>
|
||||
<Style
|
||||
TargetType="TextBlock"
|
||||
BasedOn="{StaticResource BodyTextBlockStyle}">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
</shct:DescriptionTextBlock.Resources>
|
||||
</shct:DescriptionTextBlock>
|
||||
</Expander>
|
||||
</DataTemplate>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<SplitView
|
||||
IsPaneOpen="True"
|
||||
DisplayMode="Inline"
|
||||
OpenPaneLength="374">
|
||||
OpenPaneLength="200">
|
||||
<SplitView.PaneBackground>
|
||||
<SolidColorBrush Color="{StaticResource CardBackgroundFillColorSecondary}"/>
|
||||
</SplitView.PaneBackground>
|
||||
<SplitView.Pane>
|
||||
<GridView
|
||||
<ListView
|
||||
SelectionMode="Single"
|
||||
ItemsSource="{Binding Avatars}"
|
||||
Padding="16,16,0,12"
|
||||
SelectedItem="{Binding Selected,Mode=TwoWay}">
|
||||
<GridView.ItemTemplate>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<shvc:ItemIcon
|
||||
Quality="{Binding Quality,Mode=OneWay}"
|
||||
Icon="{Binding Icon,Converter={StaticResource IconConverter},Mode=OneWay}"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shci:CachedImage
|
||||
Grid.Column="0"
|
||||
Width="48"
|
||||
Height="48"
|
||||
Margin="0,0,12,12"
|
||||
Source="{Binding SideIcon,Converter={StaticResource AvatarSideIconConverter},Mode=OneWay}"/>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="1"
|
||||
Margin="12,0,0,0"
|
||||
Text="{Binding Name}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</GridView.ItemTemplate>
|
||||
</GridView>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</SplitView.Pane>
|
||||
<SplitView.Content>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="0,0,16,16">
|
||||
<!--头图-->
|
||||
<!--简介-->
|
||||
<Grid
|
||||
Margin="16,16,0,16"
|
||||
VerticalAlignment="Top">
|
||||
@@ -63,23 +189,130 @@
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shvc:ItemIcon
|
||||
Grid.Column="0"
|
||||
Height="100"
|
||||
Width="100"
|
||||
Quality="{Binding Selected.Quality,Mode=OneWay}"
|
||||
Icon="{Binding Selected.Icon,Converter={StaticResource IconConverter},Mode=OneWay}"/>
|
||||
<StackPanel Grid.Column="0">
|
||||
<Grid Margin="0,0,0,16" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shci:CachedImage
|
||||
Grid.Column="0"
|
||||
Width="27.2"
|
||||
Height="27.2"
|
||||
Source="{Binding Selected.FetterInfo.VisionBefore,Converter={StaticResource ElementNameIconConverter}}"/>
|
||||
<shci:CachedImage
|
||||
Grid.Column="1"
|
||||
Width="27.2"
|
||||
Height="27.2"
|
||||
Source="{Binding Selected.Weapon,Converter={StaticResource WeaponTypeIconConverter}}"/>
|
||||
</Grid>
|
||||
<shvc:ItemIcon
|
||||
Height="100"
|
||||
Width="100"
|
||||
Quality="{Binding Selected.Quality,Mode=OneWay}"
|
||||
Icon="{Binding Selected.Icon,Converter={StaticResource AvatarIconConverter},Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Margin="16,0,0,0"
|
||||
Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Text="{Binding Selected.Name}"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Margin="24,0,0,0"
|
||||
Text="{Binding Selected.FetterInfo.Title}"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Text="{Binding Selected.Name}"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Text="{Binding Selected.Description}"
|
||||
MaxLines="2"
|
||||
Margin="0,12,0,0"
|
||||
Text="{Binding Selected.FetterInfo.Detail}"
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
|
||||
<cwuc:UniformGrid
|
||||
Columns="4"
|
||||
ColumnSpacing="12"
|
||||
Margin="0,12,0,0">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="所属"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
TextWrapping="NoWrap"
|
||||
Text="{Binding Selected.FetterInfo.Native}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="命之座"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
TextWrapping="NoWrap"
|
||||
Text="{Binding Selected.FetterInfo.ConstellationBefore}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="生日"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
TextWrapping="NoWrap"
|
||||
Text="{Binding Selected.FetterInfo.BirthFormatted}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
</cwuc:UniformGrid>
|
||||
|
||||
<cwuc:UniformGrid
|
||||
Margin="0,12,0,0"
|
||||
ColumnSpacing="12"
|
||||
Columns="4">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="汉语 CV"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
TextWrapping="NoWrap"
|
||||
Text="{Binding Selected.FetterInfo.CvChinese}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="日语 CV"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
TextWrapping="NoWrap"
|
||||
Text="{Binding Selected.FetterInfo.CvJapanese}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="英语 CV"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
TextWrapping="NoWrap"
|
||||
Text="{Binding Selected.FetterInfo.CvEnglish}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="韩语 CV"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
TextWrapping="NoWrap"
|
||||
Text="{Binding Selected.FetterInfo.CvKorean}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
</cwuc:UniformGrid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!--属性-->
|
||||
@@ -88,75 +321,209 @@
|
||||
Header="基础数值"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch">
|
||||
<Grid Margin="16">
|
||||
<ScrollViewer
|
||||
VerticalScrollMode="Disabled"
|
||||
HorizontalScrollBarVisibility="Auto">
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<cwuc:UniformGrid
|
||||
Grid.Row="0"
|
||||
Columns="5"
|
||||
ColumnSpacing="16"
|
||||
DataContext="{Binding Selected.Property}">
|
||||
<TextBlock
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
TextWrapping="NoWrap"
|
||||
Text="等级"/>
|
||||
<TextBlock
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{Binding Properties[0], Converter={StaticResource FightPropertyConverter}}"/>
|
||||
<TextBlock
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{Binding Properties[1], Converter={StaticResource FightPropertyConverter}}"/>
|
||||
<TextBlock
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{Binding Properties[2], Converter={StaticResource FightPropertyConverter}}"/>
|
||||
<TextBlock
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{Binding Properties[3], Converter={StaticResource FightPropertyConverter}}"/>
|
||||
</cwuc:UniformGrid>
|
||||
<ItemsControl
|
||||
Margin="0,16,0,0"
|
||||
Grid.Row="1"
|
||||
ItemsSource="{Binding Selected.Property.Parameters}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<cwuc:UniformGrid
|
||||
Columns="5"
|
||||
ColumnSpacing="16">
|
||||
<TextBlock
|
||||
Text="{Binding Level}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Parameters[0],
|
||||
Converter={StaticResource FightPropertyValueFormatter},
|
||||
ConverterParameter={Binding DataContext[0],Source={StaticResource FightPropertyBindingProxy}}}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Parameters[1],
|
||||
Converter={StaticResource FightPropertyValueFormatter},
|
||||
ConverterParameter={Binding DataContext[1],Source={StaticResource FightPropertyBindingProxy}}}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Parameters[2],
|
||||
Converter={StaticResource FightPropertyValueFormatter},
|
||||
ConverterParameter={Binding DataContext[2],Source={StaticResource FightPropertyBindingProxy}}}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Parameters[3],
|
||||
Converter={StaticResource FightPropertyValueFormatter},
|
||||
ConverterParameter={Binding DataContext[3],Source={StaticResource FightPropertyBindingProxy}}}"/>
|
||||
</cwuc:UniformGrid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
</Expander>
|
||||
<TextBlock Text="天赋" Style="{StaticResource BaseTextBlockStyle}" Margin="16,16,0,0"/>
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding Selected.SkillDepot.Skills}"
|
||||
ItemTemplate="{StaticResource SkillDataTemplate}"/>
|
||||
|
||||
<!--元素爆发-->
|
||||
<Expander
|
||||
Margin="16,16,0,0"
|
||||
Header="{Binding Name}"
|
||||
DataContext="{Binding Selected.SkillDepot.EnergySkill}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<cwuc:UniformGrid
|
||||
Grid.Row="0"
|
||||
Columns="5"
|
||||
DataContext="{Binding Selected.Property}"
|
||||
x:Name="PropertyGrid">
|
||||
<TextBlock
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
TextWrapping="NoWrap"
|
||||
Text="等级"/>
|
||||
<TextBlock
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{Binding Properties[0], Converter={StaticResource FightPropertyConverter}}"/>
|
||||
<TextBlock
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{Binding Properties[1], Converter={StaticResource FightPropertyConverter}}"/>
|
||||
<TextBlock
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{Binding Properties[2], Converter={StaticResource FightPropertyConverter}}"/>
|
||||
<TextBlock
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{Binding Properties[3], Converter={StaticResource FightPropertyConverter}}"/>
|
||||
</cwuc:UniformGrid>
|
||||
<ItemsControl
|
||||
Margin="0,6,0,0"
|
||||
<shct:DescriptionTextBlock
|
||||
Margin="16,16,16,0"
|
||||
Description="{Binding Description}">
|
||||
<shct:DescriptionTextBlock.Resources>
|
||||
<Style
|
||||
TargetType="TextBlock"
|
||||
BasedOn="{StaticResource BodyTextBlockStyle}">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
</shct:DescriptionTextBlock.Resources>
|
||||
</shct:DescriptionTextBlock>
|
||||
<ScrollViewer
|
||||
Grid.Row="1"
|
||||
ItemsSource="{Binding Selected.Property.Parameters}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<cwuc:UniformGrid
|
||||
Columns="5">
|
||||
<TextBlock
|
||||
Text="{Binding Level}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Parameters[0],
|
||||
Converter={StaticResource FightPropertyValueFormatter},
|
||||
ConverterParameter={Binding DataContext[0],Source={StaticResource FightPropertyBindingProxy}}}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Parameters[1],
|
||||
Converter={StaticResource FightPropertyValueFormatter},
|
||||
ConverterParameter={Binding DataContext[1],Source={StaticResource FightPropertyBindingProxy}}}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Parameters[2],
|
||||
Converter={StaticResource FightPropertyValueFormatter},
|
||||
ConverterParameter={Binding DataContext[2],Source={StaticResource FightPropertyBindingProxy}}}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Parameters[3],
|
||||
Converter={StaticResource FightPropertyValueFormatter},
|
||||
ConverterParameter={Binding DataContext[3],Source={StaticResource FightPropertyBindingProxy}}}"/>
|
||||
</cwuc:UniformGrid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
VerticalScrollMode="Disabled"
|
||||
HorizontalScrollBarVisibility="Auto">
|
||||
<Grid>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="16"
|
||||
DataContext="{Binding Proud,Converter={StaticResource DescParamDescriptor}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ItemsControl
|
||||
Grid.Row="0"
|
||||
ItemsSource="{Binding Descriptions}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwuc:UniformGrid
|
||||
ColumnSpacing="16"
|
||||
Columns="{Binding Descriptions.Count}"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
Text="{Binding}"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<ItemsControl
|
||||
Margin="0,6,0,0"
|
||||
Grid.Row="2"
|
||||
ItemsSource="{Binding Parameters}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwuc:UniformGrid
|
||||
ColumnSpacing="16"
|
||||
Columns="{Binding Count}"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
Text="{Binding}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Expander>
|
||||
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding Selected.SkillDepot.Inherents}"
|
||||
ItemTemplate="{StaticResource InherentDataTemplate}"/>
|
||||
|
||||
<TextBlock Text="命之座" Style="{StaticResource BaseTextBlockStyle}" Margin="16,16,0,0"/>
|
||||
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding Selected.SkillDepot.Talents}"
|
||||
ItemTemplate="{StaticResource InherentDataTemplate}"/>
|
||||
|
||||
<TextBlock Text="其他" Style="{StaticResource BaseTextBlockStyle}" Margin="16,16,0,0"/>
|
||||
<!--衣装-->
|
||||
<Expander
|
||||
Margin="16,16,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="衣装">
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding Selected.Costumes}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0,0,0,-4">
|
||||
<TextBlock
|
||||
Margin="16,16,16,0"
|
||||
Text="{Binding Name}"/>
|
||||
<TextBlock
|
||||
Margin="16,8,16,16"
|
||||
Text="{Binding Description}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<MenuFlyoutSeparator Margin="16,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Expander>
|
||||
<!--资料-->
|
||||
<Expander
|
||||
Margin="16,16,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="语音">
|
||||
Header="资料">
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding Selected.FetterInfo.Fetters}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
@@ -165,10 +532,15 @@
|
||||
<TextBlock
|
||||
Margin="16,16,16,0"
|
||||
Text="{Binding Title}"/>
|
||||
<TextBlock
|
||||
<shct:DescriptionTextBlock
|
||||
Margin="16,8,16,16"
|
||||
Text="{Binding Context}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
Description="{Binding Context}">
|
||||
<shct:DescriptionTextBlock.Resources>
|
||||
<Style
|
||||
TargetType="TextBlock"
|
||||
BasedOn="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</shct:DescriptionTextBlock.Resources>
|
||||
</shct:DescriptionTextBlock>
|
||||
<MenuFlyoutSeparator Margin="16,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
@@ -204,4 +576,4 @@
|
||||
</SplitView.Content>
|
||||
</SplitView>
|
||||
</Grid>
|
||||
</Page>
|
||||
</Page>
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.ViewModel;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
@@ -18,4 +20,15 @@ public sealed partial class WikiAvatarPage : Microsoft.UI.Xaml.Controls.Page
|
||||
DataContext = Ioc.Default.GetRequiredService<WikiAvatarViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if (e.Parameter is INavigationData extra)
|
||||
{
|
||||
extra.NotifyNavigationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<UserControl
|
||||
x:Class="Snap.Hutao.View.UserView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
|
||||
xmlns:mxim="using:Microsoft.Xaml.Interactions.Media"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
mc:Ignorable="d">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<mxic:EventTriggerBehavior EventName="Loaded">
|
||||
@@ -26,8 +27,9 @@
|
||||
<PersonPicture
|
||||
ProfilePicture="{Binding SelectedUser.UserInfo.AvatarUrl,Mode=OneWay}"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="4,0,4,0"
|
||||
Height="40"/>
|
||||
Margin="6,2"
|
||||
Height="36"
|
||||
Width="36"/>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,2"
|
||||
@@ -42,6 +44,11 @@
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
Grid.Column="2"
|
||||
Margin="4">
|
||||
<Button.Resources>
|
||||
<shc:BindingProxy
|
||||
x:Key="ViewModelBindingProxy"
|
||||
DataContext="{Binding}"/>
|
||||
</Button.Resources>
|
||||
<Button.Flyout>
|
||||
<Flyout
|
||||
Placement="TopEdgeAlignedRight"
|
||||
@@ -124,7 +131,8 @@
|
||||
BorderThickness="0"
|
||||
BorderBrush="{x:Null}"
|
||||
Margin="12,0,0,0"
|
||||
Command="{Binding CopyCookieCommand}"
|
||||
Command="{Binding DataContext.CopyCookieCommand,Source={StaticResource ViewModelBindingProxy}}"
|
||||
CommandParameter="{Binding}"
|
||||
ToolTipService.ToolTip="复制 Cookie"/>
|
||||
<Button
|
||||
Content=""
|
||||
@@ -135,7 +143,7 @@
|
||||
BorderThickness="0"
|
||||
BorderBrush="{x:Null}"
|
||||
Margin="6,0,0,0"
|
||||
Command="{Binding RemoveCommand}"
|
||||
Command="{Binding DataContext.RemoveUserCommand,Source={StaticResource ViewModelBindingProxy}}"
|
||||
CommandParameter="{Binding}"
|
||||
ToolTipService.ToolTip="移除用户"/>
|
||||
</StackPanel>
|
||||
|
||||
@@ -5,7 +5,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Control.Cancellable;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
@@ -64,11 +63,6 @@ internal class AnnouncementViewModel : ObservableObject, ISupportCancellation
|
||||
set => SetProperty(ref announcement, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开界面监视器
|
||||
/// </summary>
|
||||
public Watcher OpeningUI { get; } = new(false);
|
||||
|
||||
/// <summary>
|
||||
/// 打开界面触发的命令
|
||||
/// </summary>
|
||||
@@ -81,23 +75,18 @@ internal class AnnouncementViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
using (OpeningUI.Watch())
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Announcement = await announcementService.GetAnnouncementsAsync(OpenAnnouncementUICommand, CancellationToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
logger.LogInformation($"{nameof(OpenUIAsync)} cancelled");
|
||||
}
|
||||
Announcement = await announcementService.GetAnnouncementsAsync(OpenAnnouncementUICommand, CancellationToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
logger.LogInformation($"{nameof(OpenUIAsync)} cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenAnnouncementUI(string? content)
|
||||
{
|
||||
logger.LogInformation($"{nameof(OpenAnnouncementUICommand)} Triggered");
|
||||
|
||||
if (WebView2Helper.IsSupported)
|
||||
{
|
||||
navigationService.Navigate<AnnouncementContentPage>(data: new NavigationExtra(content));
|
||||
|
||||
@@ -12,7 +12,6 @@ internal class SettingViewModel
|
||||
/// <summary>
|
||||
/// 构造一个新的测试视图模型
|
||||
/// </summary>
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
public SettingViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
@@ -10,6 +11,7 @@ using Snap.Hutao.View.Dialog;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
@@ -23,7 +25,6 @@ internal class UserViewModel : ObservableObject
|
||||
|
||||
private readonly IUserService userService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ICommand removeUserCommandCache;
|
||||
|
||||
private User? selectedUser;
|
||||
private ObservableCollection<User>? userInfos;
|
||||
@@ -41,8 +42,8 @@ internal class UserViewModel : ObservableObject
|
||||
|
||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||
AddUserCommand = asyncRelayCommandFactory.Create(AddUserAsync);
|
||||
|
||||
removeUserCommandCache = asyncRelayCommandFactory.Create<User>(RemoveUserAsync);
|
||||
RemoveUserCommand = asyncRelayCommandFactory.Create<User>(RemoveUserAsync);
|
||||
CopyCookieCommand = new RelayCommand<User>(CopyCookie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -75,6 +76,16 @@ internal class UserViewModel : ObservableObject
|
||||
/// </summary>
|
||||
public ICommand AddUserCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 移除用户命令
|
||||
/// </summary>
|
||||
public ICommand RemoveUserCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 复制Cookie命令
|
||||
/// </summary>
|
||||
public ICommand CopyCookieCommand { get; }
|
||||
|
||||
private static bool TryValidateCookie(IDictionary<string, string> map, [NotNullWhen(true)] out IDictionary<string, string>? filteredCookie)
|
||||
{
|
||||
int validFlag = 4;
|
||||
@@ -104,7 +115,7 @@ internal class UserViewModel : ObservableObject
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
Users = await userService.GetInitializedUsersAsync(removeUserCommandCache);
|
||||
Users = await userService.GetInitializedUsersAsync();
|
||||
SelectedUser = userService.CurrentUser;
|
||||
}
|
||||
|
||||
@@ -121,11 +132,7 @@ internal class UserViewModel : ObservableObject
|
||||
if (TryValidateCookie(cookieMap, out IDictionary<string, string>? filteredCookie))
|
||||
{
|
||||
string simplifiedCookie = string.Join(';', filteredCookie.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
User user = new()
|
||||
{
|
||||
Cookie = simplifiedCookie,
|
||||
RemoveCommand = removeUserCommandCache,
|
||||
};
|
||||
User user = new() { Cookie = simplifiedCookie };
|
||||
|
||||
switch (await userService.TryAddUserAsync(user, filteredCookie[AccountIdKey]))
|
||||
{
|
||||
@@ -160,4 +167,26 @@ internal class UserViewModel : ObservableObject
|
||||
infoBarService.Success($"用户 [{user.UserInfo!.Nickname}] 成功移除");
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyCookie(User? user)
|
||||
{
|
||||
if (User.IsNone(user))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
try
|
||||
{
|
||||
DataPackage content = new();
|
||||
content.SetText(Must.NotNull(user.Cookie!));
|
||||
Clipboard.SetContent(content);
|
||||
|
||||
infoBarService.Success($"{user.UserInfo!.Nickname} 的 Cookie 复制成功");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
infoBarService.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ using Snap.Hutao.Web.Hutao.Model;
|
||||
using Snap.Hutao.Web.Hutao.Model.Post;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
@@ -325,46 +324,6 @@ internal class HutaoClient : ISupportAsyncInitialization
|
||||
return await response.Content.ReadFromJsonAsync<Response<string>>(jsonSerializerOptions, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步上传物品所有物品
|
||||
/// </summary>
|
||||
/// <param name="characters">角色详细信息</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>响应</returns>
|
||||
[Obsolete("不再强制要求上传物品")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal async Task<Response<string>?> UploadItemsAsync(List<Character> characters, CancellationToken token = default)
|
||||
{
|
||||
Verify.Operation(IsInitialized, "必须在初始化后才能调用其他方法");
|
||||
|
||||
IEnumerable<Item> avatars = characters
|
||||
.Select(avatar => new Item(avatar.Id, avatar.Name, avatar.Icon))
|
||||
.DistinctBy(item => item.Id);
|
||||
|
||||
IEnumerable<Item> weapons = characters
|
||||
.Select(avatar => avatar.Weapon)
|
||||
.Select(weapon => new Item(weapon.Id, weapon.Name, weapon.Icon))
|
||||
.DistinctBy(item => item.Id);
|
||||
|
||||
IEnumerable<Item> reliquaries = characters
|
||||
.Select(avatars => avatars.Reliquaries)
|
||||
.Flatten()
|
||||
.Where(relic => relic.Position == ReliquaryPosition.FlowerOfLife)
|
||||
.DistinctBy(relic => relic.Id)
|
||||
.Select(relic => new Item(relic.ReliquarySet.Id, relic.ReliquarySet.Name, relic.Icon));
|
||||
|
||||
GenshinItemWrapper? data = new(avatars, weapons, reliquaries);
|
||||
JsonSerializerOptions? option = Ioc.Default.GetService<JsonSerializerOptions>();
|
||||
|
||||
HttpResponseMessage? response = await httpClient
|
||||
.PostAsJsonAsync($"{HutaoAPI}/GenshinItem/Upload", data, jsonSerializerOptions, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return await response.Content
|
||||
.ReadFromJsonAsync<Response<string>>(jsonSerializerOptions, token)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private class Auth
|
||||
{
|
||||
public Auth(string appid, string secret)
|
||||
|
||||
Reference in New Issue
Block a user