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.Text;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
@@ -19,6 +20,9 @@ namespace Snap.Hutao.SourceGeneration.DedendencyInjection;
[Generator]
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/>
public void Initialize(GeneratorInitializationContext context)
{
@@ -63,9 +67,14 @@ internal static partial class ServiceCollectionExtensions
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
.GetAttributes()
@@ -77,11 +86,11 @@ internal static partial class ServiceCollectionExtensions
string injectAsName = injectAs.ToCSharpString();
switch (injectAsName)
{
case "Snap.Hutao.Core.DependencyInjection.InjectAs.Singleton":
sourceCodeBuilder.Append(@" .AddSingleton(");
case InjectAsSingletonName:
lineBuilder.Append(@" .AddSingleton(");
break;
case "Snap.Hutao.Core.DependencyInjection.InjectAs.Transient":
sourceCodeBuilder.Append(@" .AddTransient(");
case InjectAsTransientName:
lineBuilder.Append(@" .AddTransient(");
break;
default:
throw new InvalidOperationException($"非法的InjectAs值: [{injectAsName}]。");
@@ -90,10 +99,17 @@ internal static partial class ServiceCollectionExtensions
if (arguments.Length == 2)
{
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="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="CompatCornerRadiusTop">4,4,0,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
{
get => (double)GetValue(TargetWidthProperty);
set => SetValue(TargetWidthProperty, value);
}
@@ -31,15 +30,14 @@ internal class AutoHeightBehavior : BehaviorBase<FrameworkElement>
public double TargetHeight
{
get => (double)GetValue(TargetHeightProperty);
set => SetValue(TargetHeightProperty, value);
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
AssociatedObject.SizeChanged += OnSizeChanged;
UpdateElementHeight();
AssociatedObject.SizeChanged += OnSizeChanged;
}
/// <inheritdoc/>

View File

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

View File

@@ -27,6 +27,9 @@ public sealed partial class MainWindow : Window
private void MainWindowClosed(object sender, WindowEventArgs args)
{
// 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.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Input;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Windows.ApplicationModel.DataTransfer;
namespace Snap.Hutao.Model.Entity;
@@ -21,6 +24,7 @@ public class User : Observable
/// 无用户
/// </summary>
public static readonly User None = new();
private bool isInitialized = false;
private UserGameRole? selectedUserGameRole;
/// <summary>
@@ -68,6 +72,12 @@ public class User : Observable
[NotMapped]
public ICommand? RemoveCommand { get; set; }
/// <summary>
/// 复制Cookie命令
/// </summary>
[NotMapped]
public ICommand? CopyCookieCommand { get; set; }
/// <summary>
/// 判断用户是否为空用户
/// </summary>
@@ -110,6 +120,12 @@ public class User : Observable
return false;
}
if (isInitialized)
{
return true;
}
CopyCookieCommand = new RelayCommand(CopyCookie);
Must.NotNull(RemoveCommand!);
UserInfo = await userClient
@@ -122,6 +138,8 @@ public class User : Observable
SelectedUserGameRole = UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
isInitialized = true;
return UserInfo != null && UserGameRoles.Any();
}
@@ -141,4 +159,21 @@ public class User : Observable
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>
/// <param name="user">待添加的用户</param>
/// <returns>用户初始化是否成功</returns>
@@ -37,7 +38,8 @@ public interface IUserService
/// 异步移除用户
/// </summary>
/// <param name="user">待移除的用户</param>
void RemoveUser(User user);
/// <returns>任务</returns>
Task RemoveUserAsync(User user);
/// <summary>
/// 将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>
/// <param name="pageType">指定的页面类型</param>
/// <param name="isSyncTabRequested">是否同步标签,当在代码中调用时应设为 true</param>
/// <param name="data">要传递的数据</param>
/// <param name="isSyncTabRequested">是否同步标签,当在代码中调用时应设为 true</param>
/// <returns>是否导航成功</returns>
NavigationResult Navigate(Type pageType, bool isSyncTabRequested = false, NavigationExtra? data = null);
NavigationResult Navigate(Type pageType, INavigationAwaiter data, bool isSyncTabRequested = false);
/// <summary>
/// 导航到指定类型的页面
/// </summary>
/// <typeparam name="T">指定的页面类型</typeparam>
/// <param name="isSyncTabRequested">是否同步标签,当在代码中调用时应设为 true</param>
/// <param name="data">要传递的数据</param>
/// <param name="isSyncTabRequested">是否同步标签,当在代码中调用时应设为 true</param>
/// <returns>是否导航成功</returns>
NavigationResult Navigate<T>(bool isSyncTabRequested = false, NavigationExtra? data = null)
NavigationResult Navigate<T>(INavigationAwaiter data, bool isSyncTabRequested = false)
where T : Page;
/// <summary>
/// 异步的导航到指定类型的页面
/// </summary>
/// <typeparam name="TPage">指定的页面类型</typeparam>
/// <param name="syncNavigationViewItem">是否同步标签,当在代码中调用时应设为 true</param>
/// <param name="data">要传递的数据</param>
/// <param name="syncNavigationViewItem">是否同步标签,当在代码中调用时应设为 true</param>
/// <returns>是否导航成功</returns>
Task<NavigationResult> NavigateAsync<TPage>(bool syncNavigationViewItem = false, NavigationExtra? data = null)
Task<NavigationResult> NavigateAsync<TPage>(INavigationAwaiter data, bool syncNavigationViewItem = false)
where TPage : Page;
/// <summary>

View File

@@ -6,8 +6,13 @@ namespace Snap.Hutao.Service.Abstraction.Navigation;
/// <summary>
/// 导航额外信息
/// </summary>
public class NavigationExtra
public class NavigationExtra : INavigationExtra, INavigationAwaiter
{
/// <summary>
/// 任务完成源
/// </summary>
private readonly TaskCompletionSource navigationCompletedTaskCompletionSource = new();
/// <summary>
/// 构造一个新的导航额外信息
/// </summary>
@@ -17,13 +22,24 @@ public class NavigationExtra
Data = data;
}
/// <summary>
/// 数据
/// </summary>
/// <inheritdoc/>
public object? Data { get; set; }
/// <summary>
/// 任务完成源
/// </summary>
public TaskCompletionSource NavigationCompletedTaskCompletionSource { get; } = new();
}
/// <inheritdoc/>
public Task WaitForCompletionAsync()
{
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>
/// <param name="infobarService">信息条服务</param>
/// <param name="infoBarService">信息条服务</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;
}
@@ -93,7 +93,7 @@ internal class NavigationService : INavigationService
}
/// <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();
@@ -123,22 +123,28 @@ internal class NavigationService : INavigationService
}
/// <inheritdoc/>
public NavigationResult Navigate<TPage>(bool syncNavigationViewItem = false, NavigationExtra? data = null)
public NavigationResult Navigate<TPage>(INavigationAwaiter data, bool syncNavigationViewItem = false)
where TPage : Page
{
return Navigate(typeof(TPage), syncNavigationViewItem, data);
return Navigate(typeof(TPage), data, syncNavigationViewItem);
}
/// <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
{
data ??= new NavigationExtra();
NavigationResult result = Navigate<TPage>(syncNavigationViewItem, data);
NavigationResult result = Navigate<TPage>(data, syncNavigationViewItem);
if (result is NavigationResult.Succeed)
{
await data.NavigationCompletedTaskCompletionSource.Task;
try
{
await data.WaitForCompletionAsync();
}
catch (AggregateException)
{
return NavigationResult.Failed;
}
}
return result;
@@ -158,7 +164,8 @@ internal class NavigationService : INavigationService
? typeof(SettingPage)
: 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)

View File

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

View File

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

View File

@@ -27,6 +27,6 @@ public sealed partial class MainView : UserControl
navigationService = Ioc.Default.GetRequiredService<INavigationService>();
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);
if (e.Parameter is NavigationExtra extra)
if (e.Parameter is INavigationExtra extra)
{
targetContent = extra.Data as string;
LoadAnnouncementAsync(extra).Forget();
}
}
private async Task LoadAnnouncementAsync(NavigationExtra extra)
private async Task LoadAnnouncementAsync(INavigationExtra extra)
{
try
{
@@ -51,12 +51,13 @@ openInWebview: function(url){ location.href = url }}";
await WebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(MihoyoSDKDefinition);
WebView.CoreWebView2.WebMessageReceived += (_, e) => Browser.Open(e.TryGetWebMessageAsString);
}
catch
catch (Exception ex)
{
extra.NotifyNavigationException(ex);
return;
}
WebView.NavigateToString(targetContent);
extra.NavigationCompletedTaskCompletionSource.TrySetResult();
extra.NotifyNavigationCompleted();
}
}

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.Threading;
using Snap.Hutao.Factory.Abstraction;
@@ -23,8 +22,7 @@ internal class UserViewModel : ObservableObject
{
private readonly IUserService userService;
private readonly IInfoBarService infoBarService;
private ICommand removeUserCommand;
private readonly ICommand removeUserCommandCache;
private User? selectedUser;
private ObservableCollection<User>? userInfos;
@@ -43,7 +41,7 @@ internal class UserViewModel : ObservableObject
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
AddUserCommand = asyncRelayCommandFactory.Create<Flyout>(AddUserAsync);
removeUserCommand = new RelayCommand<User>(RemoveUser);
removeUserCommandCache = asyncRelayCommandFactory.Create<User>(RemoveUserAsync);
}
/// <summary>
@@ -76,46 +74,36 @@ internal class UserViewModel : ObservableObject
/// </summary>
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;
filteredCookie = new();
filteredCookie = new SortedDictionary<string, string>();
// O(1) to validate cookie
foreach ((string key, string value) in map)
{
if (key == "account_id")
{
validFlag--;
filteredCookie[key] = value;
}
if (key == "cookie_token")
{
validFlag--;
filteredCookie[key] = value;
}
if (key == "ltoken")
{
validFlag--;
filteredCookie[key] = value;
}
if (key == "ltuid")
if (key == "account_id" || key == "cookie_token" || key == "ltoken" || key == "ltuid")
{
validFlag--;
filteredCookie[key] = value;
}
}
return validFlag == 0;
if (validFlag == 0)
{
return true;
}
else
{
filteredCookie = null;
return false;
}
}
private async Task OpenUIAsync()
{
Users = await userService.GetInitializedUsersAsync(removeUserCommand);
Users = await userService.GetInitializedUsersAsync(removeUserCommandCache);
SelectedUser = userService.CurrentUser;
}
@@ -131,28 +119,37 @@ internal class UserViewModel : ObservableObject
{
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}"));
User user = new()
{
Cookie = simplifiedCookie,
RemoveCommand = removeUserCommand,
RemoveCommand = removeUserCommandCache,
};
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))
{
userService.RemoveUser(user);
await userService.RemoveUserAsync(user);
infoBarService.Success($"成功移除用户 [{user.UserInfo!.Nickname}]");
}
}
}

View File

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

View File

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

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
.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="message">消息</param>
/// <param name="data">数据</param>
[JsonConstructor]
public Response(int returnCode, string message, TData? data)
: base(returnCode, message)
{