style improvement

This commit is contained in:
DismissedLight
2022-06-23 15:23:19 +08:00
parent 19ab4fef55
commit c4602f891f
25 changed files with 430 additions and 255 deletions

View File

@@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using System; using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -19,6 +20,9 @@ namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
[Generator] [Generator]
public class InjectionGenerator : ISourceGenerator public class InjectionGenerator : ISourceGenerator
{ {
private const string InjectAsSingletonName = "Snap.Hutao.Core.DependencyInjection.InjectAs.Singleton";
private const string InjectAsTransientName = "Snap.Hutao.Core.DependencyInjection.InjectAs.Transient";
/// <inheritdoc/> /// <inheritdoc/>
public void Initialize(GeneratorInitializationContext context) public void Initialize(GeneratorInitializationContext context)
{ {
@@ -63,9 +67,14 @@ internal static partial class ServiceCollectionExtensions
private static void FillWithInjectionServices(InjectionSyntaxContextReceiver receiver, StringBuilder sourceCodeBuilder) private static void FillWithInjectionServices(InjectionSyntaxContextReceiver receiver, StringBuilder sourceCodeBuilder)
{ {
foreach (INamedTypeSymbol classSymbol in receiver.Classes.OrderByDescending(symbol => symbol.ToDisplayString())) List<string> lines = new();
StringBuilder lineBuilder = new();
foreach (INamedTypeSymbol classSymbol in receiver.Classes)
{ {
sourceCodeBuilder.Append("\r\n"); lineBuilder
.Clear()
.Append("\r\n");
AttributeData injectionInfo = classSymbol AttributeData injectionInfo = classSymbol
.GetAttributes() .GetAttributes()
@@ -77,11 +86,11 @@ internal static partial class ServiceCollectionExtensions
string injectAsName = injectAs.ToCSharpString(); string injectAsName = injectAs.ToCSharpString();
switch (injectAsName) switch (injectAsName)
{ {
case "Snap.Hutao.Core.DependencyInjection.InjectAs.Singleton": case InjectAsSingletonName:
sourceCodeBuilder.Append(@" .AddSingleton("); lineBuilder.Append(@" .AddSingleton(");
break; break;
case "Snap.Hutao.Core.DependencyInjection.InjectAs.Transient": case InjectAsTransientName:
sourceCodeBuilder.Append(@" .AddTransient("); lineBuilder.Append(@" .AddTransient(");
break; break;
default: default:
throw new InvalidOperationException($"非法的InjectAs值: [{injectAsName}]。"); throw new InvalidOperationException($"非法的InjectAs值: [{injectAsName}]。");
@@ -90,10 +99,17 @@ internal static partial class ServiceCollectionExtensions
if (arguments.Length == 2) if (arguments.Length == 2)
{ {
TypedConstant interfaceType = arguments[1]; TypedConstant interfaceType = arguments[1];
sourceCodeBuilder.Append($"{interfaceType.ToCSharpString()}, "); lineBuilder.Append($"{interfaceType.ToCSharpString()}, ");
} }
sourceCodeBuilder.Append($"typeof({classSymbol.ToDisplayString()}))"); lineBuilder.Append($"typeof({classSymbol.ToDisplayString()}))");
lines.Add(lineBuilder.ToString());
}
foreach (string line in lines.OrderByDescending(x => x))
{
sourceCodeBuilder.Append(line);
} }
} }
} }

View File

@@ -17,6 +17,9 @@
<StaticResource x:Key="WindowCaptionForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="WindowCaptionForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="WindowCaptionForegroundDisabled" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="WindowCaptionForegroundDisabled" ResourceKey="SystemControlForegroundBaseHighBrush" />
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>
<Thickness x:Key="InfoBarContentRootPadding">16,0,0,0</Thickness>
<CornerRadius x:Key="CompatCornerRadius">4</CornerRadius> <CornerRadius x:Key="CompatCornerRadius">4</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusTop">4,4,0,0</CornerRadius> <CornerRadius x:Key="CompatCornerRadiusTop">4,4,0,0</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusRight">0,4,4,0</CornerRadius> <CornerRadius x:Key="CompatCornerRadiusRight">0,4,4,0</CornerRadius>

View File

@@ -21,7 +21,6 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
public double TargetWidth public double TargetWidth
{ {
get => (double)GetValue(TargetWidthProperty); get => (double)GetValue(TargetWidthProperty);
set => SetValue(TargetWidthProperty, value); set => SetValue(TargetWidthProperty, value);
} }
@@ -31,15 +30,14 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
public double TargetHeight public double TargetHeight
{ {
get => (double)GetValue(TargetHeightProperty); get => (double)GetValue(TargetHeightProperty);
set => SetValue(TargetHeightProperty, value); set => SetValue(TargetHeightProperty, value);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAssociatedObjectLoaded() protected override void OnAssociatedObjectLoaded()
{ {
AssociatedObject.SizeChanged += OnSizeChanged;
UpdateElementHeight(); UpdateElementHeight();
AssociatedObject.SizeChanged += OnSizeChanged;
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -27,7 +27,7 @@ public class CancellablePage : Page
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{ {
viewLoadingConcellationTokenSource.Cancel();
base.OnNavigatingFrom(e); base.OnNavigatingFrom(e);
viewLoadingConcellationTokenSource.Cancel();
} }
} }

View File

@@ -27,6 +27,9 @@ public sealed partial class MainWindow : Window
private void MainWindowClosed(object sender, WindowEventArgs args) private void MainWindowClosed(object sender, WindowEventArgs args)
{ {
// save datebase // save datebase
Ioc.Default.GetRequiredService<AppDbContext>().SaveChanges(); AppDbContext appDbContext = Ioc.Default.GetRequiredService<AppDbContext>();
int changes = appDbContext.SaveChanges();
Verify.Operation(changes == 0, "存在可避免的未经处理的数据库更改");
} }
} }

View File

@@ -1,13 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.Input;
using Snap.Hutao.Extension; using Snap.Hutao.Extension;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding; using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using Windows.ApplicationModel.DataTransfer;
namespace Snap.Hutao.Model.Entity; namespace Snap.Hutao.Model.Entity;
@@ -21,6 +24,7 @@ public class User : Observable
/// 无用户 /// 无用户
/// </summary> /// </summary>
public static readonly User None = new(); public static readonly User None = new();
private bool isInitialized = false;
private UserGameRole? selectedUserGameRole; private UserGameRole? selectedUserGameRole;
/// <summary> /// <summary>
@@ -68,6 +72,12 @@ public class User : Observable
[NotMapped] [NotMapped]
public ICommand? RemoveCommand { get; set; } public ICommand? RemoveCommand { get; set; }
/// <summary>
/// 复制Cookie命令
/// </summary>
[NotMapped]
public ICommand? CopyCookieCommand { get; set; }
/// <summary> /// <summary>
/// 判断用户是否为空用户 /// 判断用户是否为空用户
/// </summary> /// </summary>
@@ -110,6 +120,12 @@ public class User : Observable
return false; return false;
} }
if (isInitialized)
{
return true;
}
CopyCookieCommand = new RelayCommand(CopyCookie);
Must.NotNull(RemoveCommand!); Must.NotNull(RemoveCommand!);
UserInfo = await userClient UserInfo = await userClient
@@ -122,6 +138,8 @@ public class User : Observable
SelectedUserGameRole = UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen); SelectedUserGameRole = UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
isInitialized = true;
return UserInfo != null && UserGameRoles.Any(); return UserInfo != null && UserGameRoles.Any();
} }
@@ -141,4 +159,21 @@ public class User : Observable
return user; return user;
} }
} }
private void CopyCookie()
{
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
try
{
DataPackage content = new();
content.SetText(Must.NotNull(Cookie!));
Clipboard.SetContent(content);
infoBarService.Success($"{UserInfo?.Nickname} 的 Cookie 复制成功");
}
catch (Exception e)
{
infoBarService.Error(e);
}
}
} }

View File

@@ -28,6 +28,7 @@ public interface IUserService
/// <summary> /// <summary>
/// 异步添加用户 /// 异步添加用户
/// 通常用户是未初始化的
/// </summary> /// </summary>
/// <param name="user">待添加的用户</param> /// <param name="user">待添加的用户</param>
/// <returns>用户初始化是否成功</returns> /// <returns>用户初始化是否成功</returns>
@@ -37,7 +38,8 @@ public interface IUserService
/// 异步移除用户 /// 异步移除用户
/// </summary> /// </summary>
/// <param name="user">待移除的用户</param> /// <param name="user">待移除的用户</param>
void RemoveUser(User user); /// <returns>任务</returns>
Task RemoveUserAsync(User user);
/// <summary> /// <summary>
/// 将cookie的字符串形式转换为字典 /// 将cookie的字符串形式转换为字典

View File

@@ -0,0 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Abstraction.Navigation;
/// <summary>
/// 表示导航等待器
/// </summary>
public interface INavigationAwaiter
{
/// <summary>
/// 默认的等待器
/// </summary>
static readonly INavigationAwaiter Default = new NavigationExtra();
/// <summary>
/// 等待导航完成,或直到抛出异常
/// </summary>
/// <returns>导航完成的任务</returns>
Task WaitForCompletionAsync();
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Abstraction.Navigation;
public interface INavigationExtra
{
/// <summary>
/// 数据
/// </summary>
object? Data { get; set; }
/// <summary>
/// 通知导航服务导航已经结束
/// </summary>
void NotifyNavigationCompleted();
/// <summary>
/// 通知导航服务导航异常
/// </summary>
/// <param name="exception">异常</param>
void NotifyNavigationException(Exception exception);
}

View File

@@ -41,29 +41,29 @@ public interface INavigationService
/// 导航到指定类型的页面 /// 导航到指定类型的页面
/// </summary> /// </summary>
/// <param name="pageType">指定的页面类型</param> /// <param name="pageType">指定的页面类型</param>
/// <param name="isSyncTabRequested">是否同步标签,当在代码中调用时应设为 true</param>
/// <param name="data">要传递的数据</param> /// <param name="data">要传递的数据</param>
/// <param name="isSyncTabRequested">是否同步标签,当在代码中调用时应设为 true</param>
/// <returns>是否导航成功</returns> /// <returns>是否导航成功</returns>
NavigationResult Navigate(Type pageType, bool isSyncTabRequested = false, NavigationExtra? data = null); NavigationResult Navigate(Type pageType, INavigationAwaiter data, bool isSyncTabRequested = false);
/// <summary> /// <summary>
/// 导航到指定类型的页面 /// 导航到指定类型的页面
/// </summary> /// </summary>
/// <typeparam name="T">指定的页面类型</typeparam> /// <typeparam name="T">指定的页面类型</typeparam>
/// <param name="isSyncTabRequested">是否同步标签,当在代码中调用时应设为 true</param>
/// <param name="data">要传递的数据</param> /// <param name="data">要传递的数据</param>
/// <param name="isSyncTabRequested">是否同步标签,当在代码中调用时应设为 true</param>
/// <returns>是否导航成功</returns> /// <returns>是否导航成功</returns>
NavigationResult Navigate<T>(bool isSyncTabRequested = false, NavigationExtra? data = null) NavigationResult Navigate<T>(INavigationAwaiter data, bool isSyncTabRequested = false)
where T : Page; where T : Page;
/// <summary> /// <summary>
/// 异步的导航到指定类型的页面 /// 异步的导航到指定类型的页面
/// </summary> /// </summary>
/// <typeparam name="TPage">指定的页面类型</typeparam> /// <typeparam name="TPage">指定的页面类型</typeparam>
/// <param name="syncNavigationViewItem">是否同步标签,当在代码中调用时应设为 true</param>
/// <param name="data">要传递的数据</param> /// <param name="data">要传递的数据</param>
/// <param name="syncNavigationViewItem">是否同步标签,当在代码中调用时应设为 true</param>
/// <returns>是否导航成功</returns> /// <returns>是否导航成功</returns>
Task<NavigationResult> NavigateAsync<TPage>(bool syncNavigationViewItem = false, NavigationExtra? data = null) Task<NavigationResult> NavigateAsync<TPage>(INavigationAwaiter data, bool syncNavigationViewItem = false)
where TPage : Page; where TPage : Page;
/// <summary> /// <summary>

View File

@@ -6,8 +6,13 @@ namespace Snap.Hutao.Service.Abstraction.Navigation;
/// <summary> /// <summary>
/// 导航额外信息 /// 导航额外信息
/// </summary> /// </summary>
public class NavigationExtra public class NavigationExtra : INavigationExtra, INavigationAwaiter
{ {
/// <summary>
/// 任务完成源
/// </summary>
private readonly TaskCompletionSource navigationCompletedTaskCompletionSource = new();
/// <summary> /// <summary>
/// 构造一个新的导航额外信息 /// 构造一个新的导航额外信息
/// </summary> /// </summary>
@@ -17,13 +22,24 @@ public class NavigationExtra
Data = data; Data = data;
} }
/// <summary> /// <inheritdoc/>
/// 数据
/// </summary>
public object? Data { get; set; } public object? Data { get; set; }
/// <summary> /// <inheritdoc/>
/// 任务完成源 public Task WaitForCompletionAsync()
/// </summary> {
public TaskCompletionSource NavigationCompletedTaskCompletionSource { get; } = new(); return navigationCompletedTaskCompletionSource.Task;
}
/// <inheritdoc/>
public void NotifyNavigationCompleted()
{
navigationCompletedTaskCompletionSource.TrySetResult();
}
/// <inheritdoc/>
public void NotifyNavigationException(Exception exception)
{
navigationCompletedTaskCompletionSource.TrySetException(exception);
}
} }

View File

@@ -25,11 +25,11 @@ internal class NavigationService : INavigationService
/// <summary> /// <summary>
/// 构造一个新的导航服务 /// 构造一个新的导航服务
/// </summary> /// </summary>
/// <param name="infobarService">信息条服务</param> /// <param name="infoBarService">信息条服务</param>
/// <param name="logger">日志器</param> /// <param name="logger">日志器</param>
public NavigationService(IInfoBarService infobarService, ILogger<INavigationService> logger) public NavigationService(IInfoBarService infoBarService, ILogger<INavigationService> logger)
{ {
this.infoBarService = infobarService; this.infoBarService = infoBarService;
this.logger = logger; this.logger = logger;
} }
@@ -93,7 +93,7 @@ internal class NavigationService : INavigationService
} }
/// <inheritdoc/> /// <inheritdoc/>
public NavigationResult Navigate(Type pageType, bool syncNavigationViewItem = false, NavigationExtra? data = null) public NavigationResult Navigate(Type pageType, INavigationAwaiter data, bool syncNavigationViewItem = false)
{ {
Type? currentType = Frame?.Content?.GetType(); Type? currentType = Frame?.Content?.GetType();
@@ -123,22 +123,28 @@ internal class NavigationService : INavigationService
} }
/// <inheritdoc/> /// <inheritdoc/>
public NavigationResult Navigate<TPage>(bool syncNavigationViewItem = false, NavigationExtra? data = null) public NavigationResult Navigate<TPage>(INavigationAwaiter data, bool syncNavigationViewItem = false)
where TPage : Page where TPage : Page
{ {
return Navigate(typeof(TPage), syncNavigationViewItem, data); return Navigate(typeof(TPage), data, syncNavigationViewItem);
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<NavigationResult> NavigateAsync<TPage>(bool syncNavigationViewItem = false, NavigationExtra? data = null) public async Task<NavigationResult> NavigateAsync<TPage>(INavigationAwaiter data, bool syncNavigationViewItem = false)
where TPage : Page where TPage : Page
{ {
data ??= new NavigationExtra(); NavigationResult result = Navigate<TPage>(data, syncNavigationViewItem);
NavigationResult result = Navigate<TPage>(syncNavigationViewItem, data);
if (result is NavigationResult.Succeed) if (result is NavigationResult.Succeed)
{ {
await data.NavigationCompletedTaskCompletionSource.Task; try
{
await data.WaitForCompletionAsync();
}
catch (AggregateException)
{
return NavigationResult.Failed;
}
} }
return result; return result;
@@ -158,7 +164,8 @@ internal class NavigationService : INavigationService
? typeof(SettingPage) ? typeof(SettingPage)
: NavHelper.GetNavigateTo(Selected); : NavHelper.GetNavigateTo(Selected);
Navigate(Must.NotNull(targetType!), false, new(NavHelper.GetExtraData(Selected))); INavigationAwaiter navigationAwaiter = new NavigationExtra(NavHelper.GetExtraData(Selected));
Navigate(Must.NotNull(targetType!), navigationAwaiter, false);
} }
private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)

View File

@@ -24,7 +24,7 @@ internal class UserService : IUserService
private readonly UserGameRoleClient userGameRoleClient; private readonly UserGameRoleClient userGameRoleClient;
private User? currentUser; private User? currentUser;
private ObservableCollection<User>? cachedUser = null; private ObservableCollection<User>? cachedUsers = null;
/// <summary> /// <summary>
/// 构造一个新的用户服务 /// 构造一个新的用户服务
@@ -52,6 +52,7 @@ internal class UserService : IUserService
{ {
User.SetSelectionState(currentUser, false); User.SetSelectionState(currentUser, false);
appDbContext.Users.Update(currentUser); appDbContext.Users.Update(currentUser);
appDbContext.SaveChanges();
} }
} }
@@ -62,6 +63,7 @@ internal class UserService : IUserService
{ {
User.SetSelectionState(currentUser, true); User.SetSelectionState(currentUser, true);
appDbContext.Users.Update(currentUser); appDbContext.Users.Update(currentUser);
appDbContext.SaveChanges();
} }
} }
} }
@@ -72,6 +74,7 @@ internal class UserService : IUserService
if (await user.InitializeAsync(userClient, userGameRoleClient)) if (await user.InitializeAsync(userClient, userGameRoleClient))
{ {
appDbContext.Users.Add(user); appDbContext.Users.Add(user);
await appDbContext.SaveChangesAsync();
return true; return true;
} }
@@ -79,20 +82,21 @@ internal class UserService : IUserService
} }
/// <inheritdoc/> /// <inheritdoc/>
public void RemoveUser(User user) public async Task RemoveUserAsync(User user)
{ {
appDbContext.Users.Remove(user); appDbContext.Users.Remove(user);
await appDbContext.SaveChangesAsync();
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<ObservableCollection<User>> GetInitializedUsersAsync(ICommand removeCommand) public async Task<ObservableCollection<User>> GetInitializedUsersAsync(ICommand removeCommand)
{ {
if (cachedUser == null) if (cachedUsers == null)
{ {
appDbContext.Users.Load(); appDbContext.Users.Load();
cachedUser = appDbContext.Users.Local.ToObservableCollection(); cachedUsers = appDbContext.Users.Local.ToObservableCollection();
foreach (User user in cachedUser) foreach (User user in cachedUsers)
{ {
user.RemoveCommand = removeCommand; user.RemoveCommand = removeCommand;
await user.InitializeAsync(userClient, userGameRoleClient); await user.InitializeAsync(userClient, userGameRoleClient);
@@ -101,7 +105,7 @@ internal class UserService : IUserService
CurrentUser = await appDbContext.Users.SingleOrDefaultAsync(user => user.IsSelected); CurrentUser = await appDbContext.Users.SingleOrDefaultAsync(user => user.IsSelected);
} }
return cachedUser; return cachedUsers;
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -14,7 +14,7 @@
<NavigationView <NavigationView
x:Name="NavView" x:Name="NavView"
CompactPaneLength="48" CompactPaneLength="48"
OpenPaneLength="200" OpenPaneLength="244"
CompactModeThresholdWidth="128" CompactModeThresholdWidth="128"
ExpandedModeThresholdWidth="720" ExpandedModeThresholdWidth="720"
IsPaneOpen="True" IsPaneOpen="True"

View File

@@ -27,6 +27,6 @@ public sealed partial class MainView : UserControl
navigationService = Ioc.Default.GetRequiredService<INavigationService>(); navigationService = Ioc.Default.GetRequiredService<INavigationService>();
navigationService.Initialize(NavView, ContentFrame); navigationService.Initialize(NavView, ContentFrame);
navigationService.Navigate<Page.AnnouncementPage>(true); navigationService.Navigate<Page.AnnouncementPage>(INavigationAwaiter.Default, true);
} }
} }

View File

@@ -35,14 +35,14 @@ openInWebview: function(url){ location.href = url }}";
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
if (e.Parameter is NavigationExtra extra) if (e.Parameter is INavigationExtra extra)
{ {
targetContent = extra.Data as string; targetContent = extra.Data as string;
LoadAnnouncementAsync(extra).Forget(); LoadAnnouncementAsync(extra).Forget();
} }
} }
private async Task LoadAnnouncementAsync(NavigationExtra extra) private async Task LoadAnnouncementAsync(INavigationExtra extra)
{ {
try try
{ {
@@ -51,12 +51,13 @@ openInWebview: function(url){ location.href = url }}";
await WebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(MihoyoSDKDefinition); await WebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(MihoyoSDKDefinition);
WebView.CoreWebView2.WebMessageReceived += (_, e) => Browser.Open(e.TryGetWebMessageAsString); WebView.CoreWebView2.WebMessageReceived += (_, e) => Browser.Open(e.TryGetWebMessageAsString);
} }
catch catch (Exception ex)
{ {
extra.NotifyNavigationException(ex);
return; return;
} }
WebView.NavigateToString(targetContent); WebView.NavigateToString(targetContent);
extra.NavigationCompletedTaskCompletionSource.TrySetResult(); extra.NotifyNavigationCompleted();
} }
} }

View File

@@ -17,7 +17,6 @@
xmlns:shvc="using:Snap.Hutao.View.Converter" xmlns:shvc="using:Snap.Hutao.View.Converter"
mc:Ignorable="d" mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<mxi:Interaction.Behaviors> <mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="Loaded"> <mxic:EventTriggerBehavior EventName="Loaded">
<mxic:InvokeCommandAction Command="{Binding OpenUICommand}"/> <mxic:InvokeCommandAction Command="{Binding OpenUICommand}"/>
@@ -46,7 +45,7 @@
<cwucont:AdaptiveGridView <cwucont:AdaptiveGridView
cwua:ItemsReorderAnimation.Duration="0:0:0.06" cwua:ItemsReorderAnimation.Duration="0:0:0.06"
SelectionMode="None" SelectionMode="None"
DesiredWidth="320" DesiredWidth="280"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding List}" ItemsSource="{Binding List}"
Margin="0,0,2,0"> Margin="0,0,2,0">
@@ -70,7 +69,6 @@
<Border <Border
cwu:UIElementExtensions.ClipToBounds="True"> cwu:UIElementExtensions.ClipToBounds="True">
<Border <Border
CompositeMode="SourceOver"
VerticalAlignment="Top" VerticalAlignment="Top"
cwu:VisualExtensions.NormalizedCenterPoint="0.5"> cwu:VisualExtensions.NormalizedCenterPoint="0.5">
<mxi:Interaction.Behaviors> <mxi:Interaction.Behaviors>

View File

@@ -2,7 +2,6 @@
x:Class="Snap.Hutao.View.UserView" x:Class="Snap.Hutao.View.UserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Snap.Hutao.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity" xmlns:mxi="using:Microsoft.Xaml.Interactivity"
@@ -14,157 +13,188 @@
<mxic:InvokeCommandAction Command="{Binding OpenUICommand}"/> <mxic:InvokeCommandAction Command="{Binding OpenUICommand}"/>
</mxic:EventTriggerBehavior> </mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors> </mxi:Interaction.Behaviors>
<Grid Height="48"> <Grid Height="58">
<Grid Height="48"> <Grid.RowDefinitions>
<Grid.ColumnDefinitions> <RowDefinition />
<ColumnDefinition Width="auto"/> <RowDefinition Height="auto"/>
<ColumnDefinition/> </Grid.RowDefinitions>
<ColumnDefinition Width="auto"/> <Grid.ColumnDefinitions>
</Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/>
<PersonPicture <ColumnDefinition/>
ProfilePicture="{Binding SelectedUser.UserInfo.AvatarUrl,Mode=OneWay}" <ColumnDefinition Width="auto"/>
HorizontalAlignment="Left" </Grid.ColumnDefinitions>
Margin="4,0,4,0" <PersonPicture
Height="40"/> ProfilePicture="{Binding SelectedUser.UserInfo.AvatarUrl,Mode=OneWay}"
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Center" Margin="4,0,4,0"
Margin="0,0,0,2" Height="40"/>
Grid.Column="1" <TextBlock
Text="{Binding SelectedUser.UserInfo.Nickname,Mode=OneWay}" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/> Margin="0,0,0,2"
<Button Grid.Column="1"
x:Name="UsersFlyoutButton" Text="{Binding SelectedUser.UserInfo.Nickname,Mode=OneWay}"
Background="Transparent" TextTrimming="CharacterEllipsis"/>
BorderBrush="{x:Null}" <Button
Height="38.4" x:Name="UsersFlyoutButton"
Content="&#xE712;" Background="Transparent"
FontFamily="{StaticResource SymbolThemeFontFamily}" BorderBrush="{x:Null}"
Grid.Column="2" Height="38.4"
Margin="4"> Content="&#xE712;"
<Button.Flyout> FontFamily="{StaticResource SymbolThemeFontFamily}"
<Flyout Grid.Column="2"
Placement="TopEdgeAlignedRight" Margin="4">
LightDismissOverlayMode="On"> <Button.Flyout>
<Flyout.FlyoutPresenterStyle> <Flyout
<Style Placement="TopEdgeAlignedRight"
TargetType="FlyoutPresenter" LightDismissOverlayMode="On">
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"> <Flyout.FlyoutPresenterStyle>
<Setter Property="Padding" Value="0,2,0,2"/> <Style
</Style> TargetType="FlyoutPresenter"
</Flyout.FlyoutPresenterStyle> BasedOn="{StaticResource DefaultFlyoutPresenterStyle}">
<StackPanel Width="192"> <Setter Property="Padding" Value="0,2,0,2"/>
<ListView </Style>
Grid.Row="1" </Flyout.FlyoutPresenterStyle>
Margin="4" <StackPanel>
SelectionMode="Single" <TextBlock
CanReorderItems="True" Margin="10,6,0,6"
ItemsSource="{Binding SelectedUser.UserGameRoles}" Style="{StaticResource BaseTextBlockStyle}"
SelectedItem="{Binding SelectedUser.SelectedUserGameRole,Mode=TwoWay}"> Text="角色"/>
<ListView.ItemTemplate> <ListView
<DataTemplate> Grid.Row="1"
<StackPanel Padding="0,6"> Margin="4"
<TextBlock Text="{Binding Nickname}"/> SelectionMode="Single"
<TextBlock CanReorderItems="True"
Margin="0,2,0,0" ItemsSource="{Binding SelectedUser.UserGameRoles}"
Opacity="0.6" SelectedItem="{Binding SelectedUser.SelectedUserGameRole,Mode=TwoWay}">
Text="{Binding Description}" <ListView.ItemTemplate>
Style="{StaticResource CaptionTextBlockStyle}"/> <DataTemplate>
</StackPanel> <StackPanel Padding="0,6">
</DataTemplate> <TextBlock Text="{Binding Nickname}"/>
</ListView.ItemTemplate> <TextBlock
</ListView> Margin="0,2,0,0"
<MenuFlyoutSeparator/> Opacity="0.6"
<ListView Text="{Binding Description}"
MaxHeight="224" Style="{StaticResource CaptionTextBlockStyle}"/>
Grid.Row="1" </StackPanel>
Margin="4" </DataTemplate>
SelectionMode="Single" </ListView.ItemTemplate>
ItemsSource="{Binding Users}" </ListView>
SelectedItem="{Binding SelectedUser,Mode=TwoWay}"> <MenuFlyoutSeparator/>
<ListView.ItemTemplate> <TextBlock
<DataTemplate> Margin="10,6,0,6"
<Grid Padding="0,12" Background="Transparent"> Style="{StaticResource BaseTextBlockStyle}"
<Grid.ColumnDefinitions> Text="账号"/>
<ColumnDefinition Width="auto"/> <ListView
<ColumnDefinition/> MaxHeight="224"
<ColumnDefinition Width="auto"/> Grid.Row="1"
</Grid.ColumnDefinitions> Margin="4"
<PersonPicture SelectionMode="Single"
ProfilePicture="{Binding UserInfo.AvatarUrl,Mode=OneWay}" ItemsSource="{Binding Users}"
HorizontalAlignment="Left" SelectedItem="{Binding SelectedUser,Mode=TwoWay}">
Margin="0,0" <ListView.ItemTemplate>
Height="32"/> <DataTemplate>
<Grid
<TextBlock Width="200"
Margin="12,0,0,0" Padding="0,12"
Grid.Column="1" Background="Transparent">
VerticalAlignment="Center" <Grid.ColumnDefinitions>
Text="{Binding UserInfo.Nickname}"/> <ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<PersonPicture
ProfilePicture="{Binding UserInfo.AvatarUrl,Mode=OneWay}"
HorizontalAlignment="Left"
Margin="0,0"
Height="32"/>
<TextBlock
Margin="12,0,0,0"
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding UserInfo.Nickname}"/>
<StackPanel
x:Name="ButtonPanel"
Orientation="Horizontal"
Grid.Column="2"
Visibility="Collapsed">
<Button <Button
x:Name="RemoveUserButton" Content="&#xE8C8;"
Content="&#xE74D;"
FontFamily="{StaticResource SymbolThemeFontFamily}" FontFamily="{StaticResource SymbolThemeFontFamily}"
HorizontalAlignment="Right"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Grid.Column="2"
Background="Transparent" Background="Transparent"
BorderThickness="0" BorderThickness="0"
BorderBrush="{x:Null}" BorderBrush="{x:Null}"
Margin="12,0,0,0" Margin="12,0,0,0"
Visibility="Collapsed" Command="{Binding CopyCookieCommand}"
ToolTipService.ToolTip="复制 Cookie"/>
<Button
Content="&#xE74D;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
BorderBrush="{x:Null}"
Margin="6,0,0,0"
Command="{Binding RemoveCommand}" Command="{Binding RemoveCommand}"
CommandParameter="{Binding}"/> CommandParameter="{Binding}"
ToolTipService.ToolTip="移除用户"/>
</StackPanel>
<Grid.Resources> <Grid.Resources>
<Storyboard x:Name="RemoveUserButtonVisibleStoryboard"> <Storyboard x:Name="ButtonPanelVisibleStoryboard">
<ObjectAnimationUsingKeyFrames <ObjectAnimationUsingKeyFrames
Storyboard.TargetName="RemoveUserButton" Storyboard.TargetName="ButtonPanel"
Storyboard.TargetProperty="Visibility"> Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value> <DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility> <Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame> </DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
<Storyboard x:Name="RemoveUserButtonCollapsedStoryboard"> <Storyboard x:Name="ButtonPanelCollapsedStoryboard">
<ObjectAnimationUsingKeyFrames <ObjectAnimationUsingKeyFrames
Storyboard.TargetName="RemoveUserButton" Storyboard.TargetName="ButtonPanel"
Storyboard.TargetProperty="Visibility"> Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value> <DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility> <Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame> </DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</Grid.Resources> </Grid.Resources>
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="PointerEntered">
<mxim:ControlStoryboardAction Storyboard="{StaticResource RemoveUserButtonVisibleStoryboard}"/>
</mxic:EventTriggerBehavior>
<mxic:EventTriggerBehavior EventName="PointerExited">
<mxim:ControlStoryboardAction Storyboard="{StaticResource RemoveUserButtonCollapsedStoryboard}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</Grid>
</DataTemplate> <mxi:Interaction.Behaviors>
</ListView.ItemTemplate> <mxic:EventTriggerBehavior EventName="PointerEntered">
</ListView> <mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelVisibleStoryboard}"/>
<CommandBar DefaultLabelPosition="Right"> </mxic:EventTriggerBehavior>
<AppBarButton <mxic:EventTriggerBehavior EventName="PointerExited">
Icon="Add" <mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelCollapsedStoryboard}"/>
Label="添加新用户" </mxic:EventTriggerBehavior>
Command="{Binding AddUserCommand}" </mxi:Interaction.Behaviors>
CommandParameter="{x:Bind UsersFlyoutButton.Flyout}"/> </Grid>
</CommandBar>
</StackPanel> </DataTemplate>
</Flyout> </ListView.ItemTemplate>
</Button.Flyout> </ListView>
</Button> <CommandBar DefaultLabelPosition="Right">
</Grid> <AppBarButton
Icon="Add"
Label="添加新用户"
Command="{Binding AddUserCommand}"
CommandParameter="{x:Bind UsersFlyoutButton.Flyout}"/>
</CommandBar>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<NavigationViewItemSeparator
Margin="0,4,0,0"
Grid.Row="1"
Grid.ColumnSpan="3"
VerticalAlignment="Bottom"/>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -99,7 +99,7 @@ internal class AnnouncementViewModel : ObservableObject, ISupportCancellation
if (WebView2Helper.IsSupported) if (WebView2Helper.IsSupported)
{ {
navigationService.Navigate<View.Page.AnnouncementContentPage>(data: new(content)); navigationService.Navigate<View.Page.AnnouncementContentPage>(data: new NavigationExtra(content));
} }
else else
{ {

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.Threading; using Snap.Hutao.Core.Threading;
using Snap.Hutao.Factory.Abstraction; using Snap.Hutao.Factory.Abstraction;
@@ -23,8 +22,7 @@ internal class UserViewModel : ObservableObject
{ {
private readonly IUserService userService; private readonly IUserService userService;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly ICommand removeUserCommandCache;
private ICommand removeUserCommand;
private User? selectedUser; private User? selectedUser;
private ObservableCollection<User>? userInfos; private ObservableCollection<User>? userInfos;
@@ -43,7 +41,7 @@ internal class UserViewModel : ObservableObject
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync); OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
AddUserCommand = asyncRelayCommandFactory.Create<Flyout>(AddUserAsync); AddUserCommand = asyncRelayCommandFactory.Create<Flyout>(AddUserAsync);
removeUserCommand = new RelayCommand<User>(RemoveUser); removeUserCommandCache = asyncRelayCommandFactory.Create<User>(RemoveUserAsync);
} }
/// <summary> /// <summary>
@@ -76,46 +74,36 @@ internal class UserViewModel : ObservableObject
/// </summary> /// </summary>
public ICommand AddUserCommand { get; } public ICommand AddUserCommand { get; }
private static bool TryValidateCookie(IDictionary<string, string> map, [NotNullWhen(true)] out SortedDictionary<string, string>? filteredCookie) private static bool TryValidateCookie(IDictionary<string, string> map, [NotNullWhen(true)] out IDictionary<string, string>? filteredCookie)
{ {
int validFlag = 4; int validFlag = 4;
filteredCookie = new(); filteredCookie = new SortedDictionary<string, string>();
// O(1) to validate cookie // O(1) to validate cookie
foreach ((string key, string value) in map) foreach ((string key, string value) in map)
{ {
if (key == "account_id") if (key == "account_id" || key == "cookie_token" || key == "ltoken" || key == "ltuid")
{
validFlag--;
filteredCookie[key] = value;
}
if (key == "cookie_token")
{
validFlag--;
filteredCookie[key] = value;
}
if (key == "ltoken")
{
validFlag--;
filteredCookie[key] = value;
}
if (key == "ltuid")
{ {
validFlag--; validFlag--;
filteredCookie[key] = value; filteredCookie[key] = value;
} }
} }
return validFlag == 0; if (validFlag == 0)
{
return true;
}
else
{
filteredCookie = null;
return false;
}
} }
private async Task OpenUIAsync() private async Task OpenUIAsync()
{ {
Users = await userService.GetInitializedUsersAsync(removeUserCommand); Users = await userService.GetInitializedUsersAsync(removeUserCommandCache);
SelectedUser = userService.CurrentUser; SelectedUser = userService.CurrentUser;
} }
@@ -131,28 +119,37 @@ internal class UserViewModel : ObservableObject
{ {
IDictionary<string, string> map = userService.ParseCookie(result.Value); IDictionary<string, string> map = userService.ParseCookie(result.Value);
if (TryValidateCookie(map, out SortedDictionary<string, string>? filteredCookie)) if (TryValidateCookie(map, out IDictionary<string, string>? filteredCookie))
{ {
string simplifiedCookie = string.Join(';', filteredCookie.Select(kvp => $"{kvp.Key}={kvp.Value}")); string simplifiedCookie = string.Join(';', filteredCookie.Select(kvp => $"{kvp.Key}={kvp.Value}"));
User user = new() User user = new()
{ {
Cookie = simplifiedCookie, Cookie = simplifiedCookie,
RemoveCommand = removeUserCommand, RemoveCommand = removeUserCommandCache,
}; };
if (!await userService.TryAddUserAsync(user)) if (!await userService.TryAddUserAsync(user))
{ {
infoBarService.Warning("提供的Cookie无效!"); infoBarService.Warning("Cookie无法获取用户信息,请重新输入");
} }
else
{
infoBarService.Success($"成功添加用户 [{user.UserInfo!.Nickname}]");
}
}
else
{
infoBarService.Warning("提供的字符串并不是有效的Cookie请重新输入");
} }
} }
} }
private void RemoveUser(User? user) private async Task RemoveUserAsync(User? user)
{ {
if (!User.IsNone(user)) if (!User.IsNone(user))
{ {
userService.RemoveUser(user); await userService.RemoveUserAsync(user);
infoBarService.Success($"成功移除用户 [{user.UserInfo!.Nickname}]");
} }
} }
} }

View File

@@ -55,6 +55,11 @@ internal class HutaoClient : ISupportAsyncInitialization
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> InitializeAsync(CancellationToken token = default) public async Task<bool> InitializeAsync(CancellationToken token = default)
{ {
if (isInitialized)
{
return true;
}
Auth auth = new( Auth auth = new(
"08d9e212-0cb3-4d71-8ed7-003606da7b20", "08d9e212-0cb3-4d71-8ed7-003606da7b20",
"7ueWgZGn53dDhrm8L5ZRw+YWfOeSWtgQmJWquRgaygw="); "7ueWgZGn53dDhrm8L5ZRw+YWfOeSWtgQmJWquRgaygw=");
@@ -185,7 +190,7 @@ internal class HutaoClient : ISupportAsyncInitialization
} }
/// <summary> /// <summary>
/// 异步获取队伍出场次数 /// 异步获取队伍出场次数 层间
/// </summary> /// </summary>
/// <param name="token">取消令牌</param> /// <param name="token">取消令牌</param>
/// <returns>队伍出场列表</returns> /// <returns>队伍出场列表</returns>
@@ -201,7 +206,7 @@ internal class HutaoClient : ISupportAsyncInitialization
} }
/// <summary> /// <summary>
/// 异步获取队伍出场次数 /// 异步获取队伍出场次数
/// </summary> /// </summary>
/// <param name="token">取消令牌</param> /// <param name="token">取消令牌</param>
/// <returns>队伍出场列表</returns> /// <returns>队伍出场列表</returns>
@@ -217,7 +222,7 @@ internal class HutaoClient : ISupportAsyncInitialization
} }
/// <summary> /// <summary>
/// 异步获取队伍出场次数 /// 按角色列表异步获取推荐队伍
/// </summary> /// </summary>
/// <param name="floor">楼层</param> /// <param name="floor">楼层</param>
/// <param name="avatarIds">期望的角色,按期望出现顺序排序</param> /// <param name="avatarIds">期望的角色,按期望出现顺序排序</param>

View File

@@ -7,6 +7,7 @@ namespace Snap.Hutao.Web.Hutao.Model;
/// <summary> /// <summary>
/// 队伍上场率 /// 队伍上场率
/// 层间上场率
/// </summary> /// </summary>
public record TeamCombination public record TeamCombination
{ {
@@ -20,19 +21,3 @@ public record TeamCombination
/// </summary> /// </summary>
public IEnumerable<Rate<Team>> Teams { get; set; } = null!; public IEnumerable<Rate<Team>> Teams { get; set; } = null!;
} }
/// <summary>
/// 队伍上场率2
/// </summary>
public record TeamCombination2
{
/// <summary>
/// 带有层的间
/// </summary>
public int Floor { get; set; }
/// <summary>
/// 队伍
/// </summary>
public IEnumerable<Rate<Team>> Teams { get; set; } = null!;
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
namespace Snap.Hutao.Web.Hutao.Model;
/// <summary>
/// 队伍上场率2
/// 层上场率
/// </summary>
public record TeamCombination2
{
/// <summary>
/// 带有层的间
/// </summary>
public int Floor { get; set; }
/// <summary>
/// 队伍
/// </summary>
public IEnumerable<Rate<Team>> Teams { get; set; } = null!;
}

View File

@@ -27,7 +27,14 @@ public class Response : ISupportValidation
{ {
Ioc.Default Ioc.Default
.GetRequiredService<IInfoBarService>() .GetRequiredService<IInfoBarService>()
.Information(ToString()); .Error(ToString());
}
if (ReturnCode != 0)
{
Ioc.Default
.GetRequiredService<IInfoBarService>()
.Warning(ToString());
} }
} }

View File

@@ -17,6 +17,7 @@ public class Response<TData> : Response
/// <param name="returnCode">返回代码</param> /// <param name="returnCode">返回代码</param>
/// <param name="message">消息</param> /// <param name="message">消息</param>
/// <param name="data">数据</param> /// <param name="data">数据</param>
[JsonConstructor]
public Response(int returnCode, string message, TData? data) public Response(int returnCode, string message, TData? data)
: base(returnCode, message) : base(returnCode, message)
{ {