caching announcements

This commit is contained in:
DismissedLight
2022-06-17 18:21:15 +08:00
parent 23fc81bba7
commit 7342bfd590
27 changed files with 316 additions and 269 deletions

View File

@@ -2,7 +2,8 @@
x:Class="Snap.Hutao.App" x:Class="Snap.Hutao.App"
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:muxc="using:Microsoft.UI.Xaml.Controls"> xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:system="using:System">
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
@@ -16,7 +17,6 @@
<StaticResource x:Key="WindowCaptionForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="WindowCaptionForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="WindowCaptionForegroundDisabled" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="WindowCaptionForegroundDisabled" ResourceKey="SystemControlForegroundBaseHighBrush" />
<CornerRadius x:Key="WindowCornerRadius">8</CornerRadius>
<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

@@ -39,6 +39,7 @@ public partial class App : Application
{ {
IServiceProvider services = new ServiceCollection() IServiceProvider services = new ServiceCollection()
.AddLogging(builder => builder.AddDebug()) .AddLogging(builder => builder.AddDebug())
.AddMemoryCache()
.AddDatebase() .AddDatebase()
.AddHttpClients() .AddHttpClients()
.AddDefaultJsonSerializerOptions() .AddDefaultJsonSerializerOptions()

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Core.Abstraction;
/// <summary> /// <summary>
/// 可异步初始化 /// 可异步初始化
/// </summary> /// </summary>
internal interface IAsyncInitializable internal interface ISupportAsyncInitialization
{ {
/// <summary> /// <summary>
/// 是否已经初始化完成 /// 是否已经初始化完成

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Abstraction;
/// <summary>
/// 表示支持验证
/// </summary>
internal interface ISupportValidation
{
/// <summary>
/// 验证
/// </summary>
/// <returns>当前数据是否有效</returns>
public bool Validate();
}

View File

@@ -9,21 +9,21 @@ namespace Snap.Hutao.Core.Json.Converter;
/// <summary> /// <summary>
/// 实现日期的转换 /// 实现日期的转换
/// </summary> /// </summary>
internal class DateTimeConverter : JsonConverter<DateTime> internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if (reader.GetString() is string dataTimeString) if (reader.GetString() is string dataTimeString)
{ {
return DateTime.Parse(dataTimeString); return DateTimeOffset.Parse(dataTimeString);
} }
return default(DateTime); return default;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
{ {
writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss")); writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
} }

View File

@@ -9,11 +9,15 @@ namespace Snap.Hutao.Core;
/// 检测 WebView2运行时 是否存在 /// 检测 WebView2运行时 是否存在
/// 不再使用注册表检查方式 /// 不再使用注册表检查方式
/// </summary> /// </summary>
internal static class WebView2Helper internal class WebView2Helper
{ {
private static bool hasEverDetected = false; private static bool hasEverDetected = false;
private static bool isSupported = false; private static bool isSupported = false;
private WebView2Helper()
{
}
/// <summary> /// <summary>
/// 检测 WebView2 是否存在 /// 检测 WebView2 是否存在
/// </summary> /// </summary>

View File

@@ -9,13 +9,14 @@
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28"/> <RowDefinition Height="48.8"/>
<RowDefinition/> <RowDefinition/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid <view:TitleView
x:Name="TitleBarGrid" x:Name="TitleBarView">
Background="Transparent"/>
</view:TitleView>
<view:MainView Grid.Row="1"/> <view:MainView Grid.Row="1"/>
</Grid> </Grid>

View File

@@ -18,6 +18,6 @@ public sealed partial class MainWindow : Window
{ {
InitializeComponent(); InitializeComponent();
ExtendsContentIntoTitleBar = true; ExtendsContentIntoTitleBar = true;
SetTitleBar(TitleBarGrid); SetTitleBar(TitleBarView.DragableArea);
} }
} }

View File

@@ -12,10 +12,10 @@ namespace Snap.Hutao.Service.Abstraction;
public interface IAnnouncementService public interface IAnnouncementService
{ {
/// <summary> /// <summary>
/// 异步获取游戏公告与活动 /// 异步获取游戏公告与活动,通常会进行缓存
/// </summary> /// </summary>
/// <param name="openAnnouncementUICommand">打开公告时触发的命令</param> /// <param name="openAnnouncementUICommand">打开公告时触发的命令</param>
/// <param name="cancellationToken">取消令牌</param> /// <param name="cancellationToken">取消令牌</param>
/// <returns>公告包装器</returns> /// <returns>公告包装器</returns>
Task<AnnouncementWrapper> GetAnnouncementsAsync(ICommand openAnnouncementUICommand, CancellationToken cancellationToken = default); ValueTask<AnnouncementWrapper> GetAnnouncementsAsync(ICommand openAnnouncementUICommand, CancellationToken cancellationToken = default);
} }

View File

@@ -1,6 +1,7 @@
// 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 Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement; using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
using System.Collections.Generic; using System.Collections.Generic;
@@ -14,20 +15,31 @@ namespace Snap.Hutao.Service;
[Injection(InjectAs.Transient, typeof(IAnnouncementService))] [Injection(InjectAs.Transient, typeof(IAnnouncementService))]
internal class AnnouncementService : IAnnouncementService internal class AnnouncementService : IAnnouncementService
{ {
private const string CacheKey = $"{nameof(IAnnouncementService)}.Cache.{nameof(AnnouncementWrapper)}";
private readonly AnnouncementClient announcementClient; private readonly AnnouncementClient announcementClient;
private readonly IMemoryCache memoryCache;
/// <summary> /// <summary>
/// 构造一个新的公告服务 /// 构造一个新的公告服务
/// </summary> /// </summary>
/// <param name="announcementClient">公告提供器</param> /// <param name="announcementClient">公告提供器</param>
public AnnouncementService(AnnouncementClient announcementClient) /// <param name="memoryCache">缓存</param>
public AnnouncementService(AnnouncementClient announcementClient, IMemoryCache memoryCache)
{ {
this.announcementClient = announcementClient; this.announcementClient = announcementClient;
this.memoryCache = memoryCache;
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<AnnouncementWrapper> GetAnnouncementsAsync(ICommand openAnnouncementUICommand, CancellationToken cancellationToken = default) public async ValueTask<AnnouncementWrapper> GetAnnouncementsAsync(ICommand openAnnouncementUICommand, CancellationToken cancellationToken = default)
{ {
// 缓存中存在记录,直接返回
if (memoryCache.TryGetValue(CacheKey, out object? cache))
{
return Must.NotNull((cache as AnnouncementWrapper)!);
}
AnnouncementWrapper? wrapper = await announcementClient AnnouncementWrapper? wrapper = await announcementClient
.GetAnnouncementsAsync(cancellationToken) .GetAnnouncementsAsync(cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
@@ -46,7 +58,7 @@ internal class AnnouncementService : IAnnouncementService
// 将公告内容联入公告列表 // 将公告内容联入公告列表
JoinAnnouncements(openAnnouncementUICommand, contentMap, wrapper.List); JoinAnnouncements(openAnnouncementUICommand, contentMap, wrapper.List);
return wrapper; return memoryCache.Set(CacheKey, wrapper, TimeSpan.FromMinutes(30));
} }
private static void JoinAnnouncements(ICommand openAnnouncementUICommand, Dictionary<int, string> contentMap, List<AnnouncementListWrapper> announcementListWrappers) private static void JoinAnnouncements(ICommand openAnnouncementUICommand, Dictionary<int, string> contentMap, List<AnnouncementListWrapper> announcementListWrappers)

View File

@@ -103,11 +103,6 @@ internal class NavigationService : INavigationService
bool navigated = false; bool navigated = false;
try try
{ {
if (data != null && data.GetType() != typeof(NavigationExtra))
{
data = new NavigationExtra(data);
}
navigated = Frame?.Navigate(pageType, data) ?? false; navigated = Frame?.Navigate(pageType, data) ?? false;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -32,6 +32,8 @@
<None Remove="View\Page\AnnouncementContentPage.xaml" /> <None Remove="View\Page\AnnouncementContentPage.xaml" />
<None Remove="View\Page\AnnouncementPage.xaml" /> <None Remove="View\Page\AnnouncementPage.xaml" />
<None Remove="View\Page\SettingPage.xaml" /> <None Remove="View\Page\SettingPage.xaml" />
<None Remove="View\TitleView.xaml" />
<None Remove="View\UserView.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="stylecop.json" /> <AdditionalFiles Include="stylecop.json" />
@@ -55,6 +57,7 @@
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="4.5.1" /> <PackageReference Include="Microsoft.AppCenter.Analytics" Version="4.5.1" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.5.1" /> <PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.5.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.2.32" /> <PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.2.32" />
@@ -82,6 +85,16 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\SettingsUI\SettingsUI.csproj" /> <ProjectReference Include="..\..\SettingsUI\SettingsUI.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Page Update="View\UserView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\TitleView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Page Update="View\Page\AnnouncementContentPage.xaml"> <Page Update="View\Page\AnnouncementContentPage.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@@ -1,85 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using Snap.Hutao.Core;
namespace Snap.Hutao.View.Converter;
/// <summary>
/// This class converts a boolean value into an other object.
/// Can be used to convert true/false to visibility, a couple of colors, couple of images, etc.
/// </summary>
public class BoolToObjectConverter : DependencyObject, IValueConverter
{
/// <summary>
/// Identifies the <see cref="TrueValue"/> property.
/// </summary>
public static readonly DependencyProperty TrueValueProperty = Property<BoolToObjectConverter>.Depend<object>(nameof(TrueValue));
/// <summary>
/// Identifies the <see cref="FalseValue"/> property.
/// </summary>
public static readonly DependencyProperty FalseValueProperty = Property<BoolToObjectConverter>.Depend<object>(nameof(FalseValue));
/// <summary>
/// Gets or sets the value to be returned when the boolean is true
/// </summary>
public object TrueValue
{
get => GetValue(TrueValueProperty);
set => SetValue(TrueValueProperty, value);
}
/// <summary>
/// Gets or sets the value to be returned when the boolean is false
/// </summary>
public object FalseValue
{
get => GetValue(FalseValueProperty);
set => SetValue(FalseValueProperty, value);
}
/// <summary>
/// Convert a boolean value to an other object.
/// </summary>
/// <param name="value">The source data being passed to the target.</param>
/// <param name="targetType">The type of the target property, as a type reference.</param>
/// <param name="parameter">An optional parameter to be used to invert the converter logic.</param>
/// <param name="language">The language of the conversion.</param>
/// <returns>The value to be passed to the target dependency property.</returns>
public object Convert(object value, Type targetType, object parameter, string language)
{
bool boolValue = value is bool valid && valid;
// Negate if needed
if (ConvertHelper.TryParseBool(parameter))
{
boolValue = !boolValue;
}
return ConvertHelper.Convert(boolValue ? TrueValue : FalseValue, targetType);
}
/// <summary>
/// Convert back the value to a boolean
/// </summary>
/// <remarks>If the <paramref name="value"/> parameter is a reference type, <see cref="TrueValue"/> must match its reference to return true.</remarks>
/// <param name="value">The target data being passed to the source.</param>
/// <param name="targetType">The type of the target property, as a type reference (System.Type for Microsoft .NET, a TypeName helper struct for Visual C++ component extensions (C++/CX)).</param>
/// <param name="parameter">An optional parameter to be used to invert the converter logic.</param>
/// <param name="language">The language of the conversion.</param>
/// <returns>The value to be passed to the source object.</returns>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
bool result = Equals(value, ConvertHelper.Convert(TrueValue, value.GetType()));
if (ConvertHelper.TryParseBool(parameter))
{
result = !result;
}
return result;
}
}

View File

@@ -1,21 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.View.Converter;
/// <summary>
/// This class converts a boolean value into a Visibility enumeration.
/// </summary>
public class BoolToVisibilityConverter : BoolToObjectConverter
{
/// <summary>
/// Initializes a new instance of the <see cref="BoolToVisibilityConverter"/> class.
/// </summary>
public BoolToVisibilityConverter()
{
TrueValue = Visibility.Visible;
FalseValue = Visibility.Collapsed;
}
}

View File

@@ -1,6 +1,7 @@
// 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.WinUI.UI.Converters;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
namespace Snap.Hutao.View.Converter; namespace Snap.Hutao.View.Converter;
@@ -11,7 +12,7 @@ namespace Snap.Hutao.View.Converter;
public class BoolToVisibilityRevertConverter : BoolToObjectConverter public class BoolToVisibilityRevertConverter : BoolToObjectConverter
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BoolToVisibilityConverter"/> class. /// Initializes a new instance of the <see cref="BoolToVisibilityRevertConverter"/> class.
/// </summary> /// </summary>
public BoolToVisibilityRevertConverter() public BoolToVisibilityRevertConverter()
{ {

View File

@@ -6,17 +6,24 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:helper="using:Snap.Hutao.View.Helper" xmlns:helper="using:Snap.Hutao.View.Helper"
xmlns:page="using:Snap.Hutao.View.Page" xmlns:page="using:Snap.Hutao.View.Page"
xmlns:view="using:Snap.Hutao.View"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid> <Grid>
<!-- x:Bind can't get property update here seems like a WinUI 3 bug-->
<NavigationView <NavigationView
x:Name="NavView" x:Name="NavView"
CompactPaneLength="48" CompactPaneLength="48"
OpenPaneLength="172" OpenPaneLength="200"
CompactModeThresholdWidth="128" CompactModeThresholdWidth="128"
ExpandedModeThresholdWidth="720" ExpandedModeThresholdWidth="720"
IsPaneOpen="True" IsPaneOpen="True"
IsBackEnabled="{Binding ElementName=ContentFrame,Path=CanGoBack}"> IsBackEnabled="{Binding ElementName=ContentFrame,Path=CanGoBack}">
<!-- x:Bind can't get property update here seems like a WinUI 3 bug-->
<NavigationView.PaneCustomContent>
<view:UserView IsExpanded="{Binding ElementName=NavView,Path=IsPaneOpen}"/>
</NavigationView.PaneCustomContent>
<NavigationView.MenuItems> <NavigationView.MenuItems>
<NavigationViewItem Content="活动" helper:NavHelper.NavigateTo="page:AnnouncementPage"> <NavigationViewItem Content="活动" helper:NavHelper.NavigateTo="page:AnnouncementPage">

View File

@@ -4,6 +4,7 @@
using Microsoft.UI.Xaml.Navigation; using Microsoft.UI.Xaml.Navigation;
using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.Threading;
using Snap.Hutao.Core; using Snap.Hutao.Core;
using Snap.Hutao.Service.Abstraction.Navigation;
namespace Snap.Hutao.View.Page; namespace Snap.Hutao.View.Page;
@@ -33,11 +34,15 @@ openInWebview: function(url){ location.href = url }}";
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
targetContent = e.Parameter as string;
LoadAnnouncementAsync().Forget(); if (e.Parameter is NavigationExtra extra)
{
targetContent = extra.Data as string;
LoadAnnouncementAsync(extra).Forget();
}
} }
private async Task LoadAnnouncementAsync() private async Task LoadAnnouncementAsync(NavigationExtra extra)
{ {
try try
{ {
@@ -52,5 +57,6 @@ openInWebview: function(url){ location.href = url }}";
} }
WebView.NavigateToString(targetContent); WebView.NavigateToString(targetContent);
extra.NavigationCompletedTaskCompletionSource.TrySetResult();
} }
} }

View File

@@ -4,16 +4,17 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cwui="using:CommunityToolkit.WinUI.UI" xmlns:cwu="using:CommunityToolkit.WinUI.UI"
xmlns:cwua="using:CommunityToolkit.WinUI.UI.Animations" xmlns:cwua="using:CommunityToolkit.WinUI.UI.Animations"
xmlns:cwub="using:CommunityToolkit.WinUI.UI.Behaviors" xmlns:cwub="using:CommunityToolkit.WinUI.UI.Behaviors"
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls" xmlns:cwucont="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core" xmlns:cwuconv="using:CommunityToolkit.WinUI.UI.Converters"
xmlns:mxim="using:Microsoft.Xaml.Interactions.Media"
xmlns:mxi="using:Microsoft.Xaml.Interactivity" xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
xmlns:shca="using:Snap.Hutao.Control.Animation"
xmlns:shcb="using:Snap.Hutao.Control.Behavior" xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shcc="using:Snap.Hutao.Control.Cancellable" xmlns:shcc="using:Snap.Hutao.Control.Cancellable"
xmlns:shvc="using:Snap.Hutao.View.Converter" xmlns:shca="using:Snap.Hutao.Control.Animation" xmlns:shvc="using:Snap.Hutao.View.Converter"
mc:Ignorable="d" mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
@@ -23,7 +24,7 @@
</mxic:EventTriggerBehavior> </mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors> </mxi:Interaction.Behaviors>
<shcc:CancellablePage.Resources> <shcc:CancellablePage.Resources>
<shvc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/> <cwuconv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/> <shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
</shcc:CancellablePage.Resources> </shcc:CancellablePage.Resources>
<Grid> <Grid>
@@ -35,7 +36,7 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding Announcement.List}" ItemsSource="{Binding Announcement.List}"
Padding="0" Padding="0"
Margin="12,12,0,0"> Margin="12,12,0,-12">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<StackPanel> <StackPanel>
@@ -43,24 +44,24 @@
Text="{Binding TypeLabel}" Text="{Binding TypeLabel}"
Margin="0,0,0,12" Margin="0,0,0,12"
Style="{StaticResource TitleTextBlockStyle}"/> Style="{StaticResource TitleTextBlockStyle}"/>
<cwuc:AdaptiveGridView <cwucont:AdaptiveGridView
cwua:ItemsReorderAnimation.Duration="0:0:0.06" cwua:ItemsReorderAnimation.Duration="0:0:0.06"
SelectionMode="None" SelectionMode="None"
DesiredWidth="320" DesiredWidth="320"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding List}" ItemsSource="{Binding List}"
Margin="0,0,0,0"> Margin="0,0,0,0">
<cwuc:AdaptiveGridView.ItemContainerStyle> <cwucont:AdaptiveGridView.ItemContainerStyle>
<Style BasedOn="{StaticResource DefaultGridViewItemStyle}" TargetType="GridViewItem"> <Style BasedOn="{StaticResource DefaultGridViewItemStyle}" TargetType="GridViewItem">
<Setter Property="Margin" Value="0,0,12,12"/> <Setter Property="Margin" Value="0,0,12,12"/>
</Style> </Style>
</cwuc:AdaptiveGridView.ItemContainerStyle> </cwucont:AdaptiveGridView.ItemContainerStyle>
<cwuc:AdaptiveGridView.ItemTemplate> <cwucont:AdaptiveGridView.ItemTemplate>
<DataTemplate> <DataTemplate>
<Border <Border
CornerRadius="{StaticResource CompatCornerRadius}" CornerRadius="{StaticResource CompatCornerRadius}"
Background="{ThemeResource SystemControlPageBackgroundAltHighBrush}" Background="{ThemeResource SystemControlPageBackgroundAltHighBrush}"
cwui:UIElementExtensions.ClipToBounds="True"> cwu:UIElementExtensions.ClipToBounds="True">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition/>
@@ -68,10 +69,10 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!--Image Layer--> <!--Image Layer-->
<Border <Border
cwui:UIElementExtensions.ClipToBounds="True"> cwu:UIElementExtensions.ClipToBounds="True">
<Border <Border
VerticalAlignment="Top" VerticalAlignment="Top"
cwui:VisualExtensions.NormalizedCenterPoint="0.5"> cwu:VisualExtensions.NormalizedCenterPoint="0.5">
<mxi:Interaction.Behaviors> <mxi:Interaction.Behaviors>
<shcb:AutoHeightBehavior TargetWidth="1080" TargetHeight="390"/> <shcb:AutoHeightBehavior TargetWidth="1080" TargetHeight="390"/>
</mxi:Interaction.Behaviors> </mxi:Interaction.Behaviors>
@@ -111,33 +112,14 @@
Grid.Row="1" Grid.Row="1"
CornerRadius="{StaticResource CompatCornerRadiusBottom}"> CornerRadius="{StaticResource CompatCornerRadiusBottom}">
<StackPanel Margin="4" VerticalAlignment="Bottom"> <StackPanel Margin="4" VerticalAlignment="Bottom">
<Grid Margin="4,6,0,0" HorizontalAlignment="Stretch"> <TextBlock
<Grid.ColumnDefinitions> Margin="4,6,0,0"
<ColumnDefinition/> HorizontalAlignment="Stretch"
<ColumnDefinition Width="auto"/> Text="{Binding Subtitle}"
</Grid.ColumnDefinitions> Style="{StaticResource SubtitleTextBlockStyle}"
TextWrapping="NoWrap"
TextTrimming="WordEllipsis"/>
<TextBlock
Text="{Binding Subtitle}"
Style="{StaticResource SubtitleTextBlockStyle}"
TextWrapping="NoWrap"
TextTrimming="WordEllipsis"/>
<Button
x:Name="OpenAnnouncementButton"
Content="&#xE8A7;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Grid.Column="1"
Background="Transparent"
BorderThickness="0"
BorderBrush="{x:Null}"
Visibility="Collapsed"
Command="{Binding OpenAnnouncementUICommand}"
CommandParameter="{Binding Content}"/>
</Grid>
<TextBlock <TextBlock
Text="{Binding Title}" Text="{Binding Title}"
Style="{StaticResource BodyTextBlockStyle}" Style="{StaticResource BodyTextBlockStyle}"
@@ -145,6 +127,7 @@
TextTrimming="WordEllipsis" TextTrimming="WordEllipsis"
Margin="4,6,0,0" Margin="4,6,0,0"
Opacity="0.6"/> Opacity="0.6"/>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition/> <ColumnDefinition/>
@@ -167,49 +150,26 @@
Style="{StaticResource CaptionTextBlockStyle}" Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding TimeDescription}" /> Text="{Binding TimeDescription}" />
</Grid> </Grid>
</StackPanel> </StackPanel>
</Border> </Border>
</Grid> </Grid>
<Border.Resources>
<Storyboard x:Name="OpenAnnouncementButtonVisibleStoryboard">
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="OpenAnnouncementButton"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="OpenAnnouncementButtonCollapsedStoryboard">
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="OpenAnnouncementButton"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Border.Resources>
<mxi:Interaction.Behaviors> <mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="Tapped">
<mxic:InvokeCommandAction
Command="{Binding OpenAnnouncementUICommand}"
CommandParameter="{Binding Content}"/>
</mxic:EventTriggerBehavior>
<mxic:EventTriggerBehavior EventName="PointerEntered"> <mxic:EventTriggerBehavior EventName="PointerEntered">
<cwub:StartAnimationAction Animation="{Binding ElementName=ImageZoomInAnimation}" /> <cwub:StartAnimationAction Animation="{Binding ElementName=ImageZoomInAnimation}" />
<mxim:ControlStoryboardAction Storyboard="{StaticResource OpenAnnouncementButtonVisibleStoryboard}"/>
</mxic:EventTriggerBehavior> </mxic:EventTriggerBehavior>
<mxic:EventTriggerBehavior EventName="PointerExited"> <mxic:EventTriggerBehavior EventName="PointerExited">
<cwub:StartAnimationAction Animation="{Binding ElementName=ImageZoomOutAnimation}" /> <cwub:StartAnimationAction Animation="{Binding ElementName=ImageZoomOutAnimation}" />
<mxim:ControlStoryboardAction Storyboard="{StaticResource OpenAnnouncementButtonCollapsedStoryboard}"/>
</mxic:EventTriggerBehavior> </mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors> </mxi:Interaction.Behaviors>
</Border> </Border>
</DataTemplate> </DataTemplate>
</cwuc:AdaptiveGridView.ItemTemplate> </cwucont:AdaptiveGridView.ItemTemplate>
</cwuc:AdaptiveGridView> </cwucont:AdaptiveGridView>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>

View File

@@ -0,0 +1,18 @@
<UserControl
x:Class="Snap.Hutao.View.TitleView"
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"
mc:Ignorable="d"
Height="48.8">
<Grid x:Name="DragableGrid">
<TextBlock
Text="胡桃"
TextWrapping="NoWrap"
Style="{StaticResource CaptionTextBlockStyle}"
VerticalAlignment="Center"
Margin="12,0,0,0"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,29 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.View;
/// <summary>
/// 标题视图
/// </summary>
public sealed partial class TitleView : UserControl
{
/// <summary>
/// 构造一个新的标题视图
/// </summary>
public TitleView()
{
this.InitializeComponent();
}
/// <summary>
/// 获取可拖动区域
/// </summary>
public UIElement DragableArea
{
get => DragableGrid;
}
}

View File

@@ -0,0 +1,74 @@
<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:local="using:Snap.Hutao.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Height="48">
<Grid Height="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<PersonPicture
ProfilePicture="https://upload-bbs.mihoyo.com/game_record/genshin/character_icon/UI_AvatarIcon_Hutao.png"
HorizontalAlignment="Left"
Margin="4,0,4,0"
Height="40"
Initials="LB"/>
<TextBlock
VerticalAlignment="Center"
Margin="0,0,0,2"
Grid.Column="1"
Text="胡桃胡桃胡桃胡桃胡桃胡桃"
TextTrimming="CharacterEllipsis"/>
<Button
Background="Transparent"
BorderBrush="{x:Null}"
Height="38.4"
Content="&#xE712;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
Grid.Column="2"
Margin="4">
<Button.Flyout>
<Flyout
Placement="BottomEdgeAlignedRight"
LightDismissOverlayMode="On">
<Flyout.FlyoutPresenterStyle>
<Style
TargetType="FlyoutPresenter"
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}">
<Setter Property="Padding" Value="0,2,0,2"/>
</Style>
</Flyout.FlyoutPresenterStyle>
<StackPanel Width="192">
<CommandBar DefaultLabelPosition="Right">
<AppBarButton Icon="Add" Label="添加新用户"/>
</CommandBar>
<ListView
Grid.Row="1"
Margin="4"
CanReorderItems="True">
<ListViewItem Content="角色1"/>
<ListViewItem Content="角色2"/>
</ListView>
<MenuFlyoutSeparator/>
<ListView
Grid.Row="1"
Margin="4"
CanReorderItems="True">
<ListViewItem Content="用户1"/>
<ListViewItem Content="用户2"/>
<ListViewItem Content="用户3"/>
<ListViewItem Content="用户4"/>
</ListView>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core;
namespace Snap.Hutao.View;
/// <summary>
/// 用户视图
/// </summary>
public sealed partial class UserView : UserControl
{
private static readonly DependencyProperty IsExpandedProperty = Property<UserView>.Depend(nameof(IsExpanded), true);
/// <summary>
/// 构造一个新的用户视图
/// </summary>
public UserView()
{
InitializeComponent();
}
/// <summary>
/// 当前用户控件是否处于展开状态
/// </summary>
public bool IsExpanded
{
get => (bool)GetValue(IsExpandedProperty);
set => SetValue(IsExpandedProperty, value);
}
}

View File

@@ -89,18 +89,18 @@ internal class AnnouncementViewModel : ObservableObject, ISupportCancellation
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
logger.LogInformation("Open UI cancelled"); logger.LogInformation($"{nameof(OpenUIAsync)} cancelled");
} }
} }
} }
private void OpenAnnouncementUI(string? content) private void OpenAnnouncementUI(string? content)
{ {
logger.LogInformation("Open Announcement Command Triggered"); logger.LogInformation($"{nameof(OpenAnnouncementUICommand)} Triggered");
if (WebView2Helper.IsSupported) if (WebView2Helper.IsSupported)
{ {
navigationService.Navigate<View.Page.AnnouncementContentPage>(data: content); navigationService.Navigate<View.Page.AnnouncementContentPage>(data: new(content));
} }
else else
{ {

View File

@@ -1,7 +1,6 @@
// 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 Snap.Hutao.Core.Json.Converter;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Windows.Input; using System.Windows.Input;
@@ -124,15 +123,15 @@ public class Announcement : AnnouncementContent
/// 开始时间 /// 开始时间
/// </summary> /// </summary>
[JsonPropertyName("start_time")] [JsonPropertyName("start_time")]
[JsonConverter(typeof(DateTimeConverter))] [JsonConverter(typeof(Core.Json.Converter.DateTimeOffsetConverter))]
public DateTime StartTime { get; set; } public DateTimeOffset StartTime { get; set; }
/// <summary> /// <summary>
/// 结束时间 /// 结束时间
/// </summary> /// </summary>
[JsonPropertyName("end_time")] [JsonPropertyName("end_time")]
[JsonConverter(typeof(DateTimeConverter))] [JsonConverter(typeof(Core.Json.Converter.DateTimeOffsetConverter))]
public DateTime EndTime { get; set; } public DateTimeOffset EndTime { get; set; }
/// <summary> /// <summary>
/// 类型 /// 类型

View File

@@ -23,7 +23,7 @@ namespace Snap.Hutao.Web.Hutao;
/// 胡桃API客户端 /// 胡桃API客户端
/// </summary> /// </summary>
[Injection(InjectAs.Transient)] [Injection(InjectAs.Transient)]
internal class HutaoClient : IAsyncInitializable internal class HutaoClient : ISupportAsyncInitialization
{ {
private const string AuthAPIHost = "https://auth.snapgenshin.com"; private const string AuthAPIHost = "https://auth.snapgenshin.com";
private const string HutaoAPI = "https://hutao-api.snapgenshin.com"; private const string HutaoAPI = "https://hutao-api.snapgenshin.com";
@@ -342,7 +342,7 @@ internal class HutaoClient : IAsyncInitializable
await playerRecord.UploadRecordAsync(this, token); await playerRecord.UploadRecordAsync(this, token);
} }
await resultAsyncFunc(resp ?? Response.Response.CreateForException($"{role.GameUid}-记录提交失败。")); // await resultAsyncFunc(resp ?? Response.Response.CreateForException($"{role.GameUid}-记录提交失败。"));
} }
} }
} }

View File

@@ -1,6 +1,8 @@
// 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 Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Service.Abstraction;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Snap.Hutao.Web.Response; namespace Snap.Hutao.Web.Response;
@@ -8,8 +10,27 @@ namespace Snap.Hutao.Web.Response;
/// <summary> /// <summary>
/// 提供 <see cref="Response{T}"/> 的非泛型基类 /// 提供 <see cref="Response{T}"/> 的非泛型基类
/// </summary> /// </summary>
public class Response public class Response : ISupportValidation
{ {
/// <summary>
/// 构造一个新的响应
/// </summary>
/// <param name="returnCode">返回代码</param>
/// <param name="message">消息</param>
[JsonConstructor]
public Response(int returnCode, string message)
{
ReturnCode = returnCode;
Message = message;
if (!Validate())
{
Ioc.Default
.GetRequiredService<IInfoBarService>()
.Information(ToString());
}
}
/// <summary> /// <summary>
/// 返回代码 /// 返回代码
/// </summary> /// </summary>
@@ -32,18 +53,10 @@ public class Response
return response is not null && response.ReturnCode == 0; return response is not null && response.ReturnCode == 0;
} }
/// <summary> /// <inheritdoc/>
/// 构造一个失败的响应 public bool Validate()
/// </summary>
/// <param name="message">消息</param>
/// <returns>响应</returns>
public static Response CreateForException(string message)
{ {
return new Response() return Enum.IsDefined(typeof(KnownReturnCode), ReturnCode);
{
ReturnCode = (int)KnownReturnCode.InternalFailure,
Message = message,
};
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -11,50 +11,21 @@ namespace Snap.Hutao.Web.Response;
/// <typeparam name="TData">数据类型</typeparam> /// <typeparam name="TData">数据类型</typeparam>
public class Response<TData> : Response public class Response<TData> : Response
{ {
/// <summary>
/// 构造一个新的 Mihoyo 标准API响应
/// </summary>
/// <param name="returnCode">返回代码</param>
/// <param name="message">消息</param>
/// <param name="data">数据</param>
public Response(int returnCode, string message, TData? data)
: base(returnCode, message)
{
Data = data;
}
/// <summary> /// <summary>
/// 数据 /// 数据
/// </summary> /// </summary>
[JsonPropertyName("data")] [JsonPropertyName("data")]
public TData? Data { get; set; } public TData? Data { get; set; }
/// <summary>
/// 构造一个失败的响应
/// </summary>
/// <param name="message">消息</param>
/// <returns>响应</returns>
public static new Response<TData> CreateForException(string message)
{
return new Response<TData>()
{
ReturnCode = (int)KnownReturnCode.InternalFailure,
Message = message,
};
}
/// <summary>
/// 构造一个失败的响应
/// </summary>
/// <param name="message">消息</param>
/// <returns>响应</returns>
public static Response<TData> CreateForJsonException(string message)
{
return new Response<TData>()
{
ReturnCode = (int)KnownReturnCode.InternalFailure,
Message = message,
};
}
/// <summary>
/// 构造一个空Url的响应
/// </summary>
/// <returns>响应</returns>
public static Response<TData> CreateForEmptyUrl()
{
return new Response<TData>()
{
ReturnCode = (int)KnownReturnCode.UrlIsEmpty,
Message = "请求的 Url 不应为空",
};
}
} }