refactor wiki avatar page

This commit is contained in:
DismissedLight
2022-07-27 22:20:10 +08:00
parent 0d8b6b66df
commit a6ad24d534
31 changed files with 598 additions and 392 deletions

View File

@@ -45,12 +45,19 @@ public partial class App : Application
public static Window? Window { get => window; set => window = value; }
/// <inheritdoc cref="Application"/>
public static new App Current => (App)Application.Current;
public static new App Current
{
get => (App)Application.Current;
}
/// <summary>
/// <inheritdoc cref="Windows.Storage.ApplicationData.Current"/>
/// </summary>
public static Windows.Storage.ApplicationData AppData => Windows.Storage.ApplicationData.Current;
[SuppressMessage("", "CA1822")]
public Windows.Storage.ApplicationData AppData
{
get => Windows.Storage.ApplicationData.Current;
}
/// <summary>
/// Invoked when the application is launched.
@@ -60,12 +67,13 @@ public partial class App : Application
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
AppInstance mainInstance = AppInstance.FindOrRegisterForKey("main");
AppInstance firstInstance = AppInstance.FindOrRegisterForKey("main");
firstInstance.Activated += OnActivated;
if (!mainInstance.IsCurrent)
if (!firstInstance.IsCurrent)
{
// Redirect the activation (and args) to the "main" instance, and exit.
await mainInstance.RedirectActivationToAsync(activatedEventArgs);
await firstInstance.RedirectActivationToAsync(activatedEventArgs);
Process.GetCurrentProcess().Kill();
}
else
@@ -73,11 +81,6 @@ public partial class App : Application
Window = Ioc.Default.GetRequiredService<MainWindow>();
Window.Activate();
if (activatedEventArgs.TryGetProtocolActivatedUri(out Uri? uri))
{
Ioc.Default.GetRequiredService<IInfoBarService>().Information(uri.ToString());
}
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", AppData.TemporaryFolder.Path);
Ioc.Default
@@ -117,6 +120,14 @@ public partial class App : Application
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
}
private void OnActivated(object? sender, AppActivationArguments args)
{
if (args.TryGetProtocolActivatedUri(out Uri? uri))
{
Ioc.Default.GetRequiredService<IInfoBarService>().Information(uri.ToString());
}
}
private void XamlBindingFailed(object sender, BindingFailedEventArgs e)
{
logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message);

View File

@@ -31,6 +31,7 @@ public class CachedImage : ImageEx
try
{
Verify.Operation(imageUri.Host != string.Empty, "可能是空绑定产生的 [ms-appx:///]");
StorageFile file = await imageCache.GetFileFromCacheAsync(imageUri);
// check token state to determine whether the operation should be canceled.

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Extension;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -236,25 +237,17 @@ public abstract class CacheBase<T>
return;
}
await cacheFolderSemaphore.WaitAsync().ConfigureAwait(false);
baseFolder ??= ApplicationData.Current.TemporaryFolder;
if (string.IsNullOrWhiteSpace(cacheFolderName))
using (await cacheFolderSemaphore.EnterAsync().ConfigureAwait(false))
{
cacheFolderName = GetType().Name;
}
baseFolder ??= ApplicationData.Current.TemporaryFolder;
try
{
cacheFolder = await baseFolder
.CreateFolderAsync(cacheFolderName, CreationCollisionOption.OpenIfExists)
.AsTask()
.ConfigureAwait(false);
}
finally
{
cacheFolderSemaphore.Release();
if (string.IsNullOrWhiteSpace(cacheFolderName))
{
cacheFolderName = GetType().Name;
}
cacheFolder = await baseFolder.CreateFolderAsync(cacheFolderName, CreationCollisionOption.OpenIfExists)
.AsTask().ConfigureAwait(false);
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Media.Imaging;
using System.Collections.Generic;
using Windows.Storage;

View File

@@ -13,11 +13,12 @@ namespace Snap.Hutao.Core.Logging;
/// The provider for the <see cref="DebugLogger"/>.
/// </summary>
[ProviderAlias("Database")]
public class DatebaseLoggerProvider : ILoggerProvider
public sealed class DatebaseLoggerProvider : ILoggerProvider
{
private static readonly object LogDbContextLock = new();
// the provider is created per logger, we don't want to create to much
private static volatile LogDbContext? logDbContext;
private static readonly object logDbContextLock = new();
private static LogDbContext LogDbContext
{
@@ -25,7 +26,7 @@ public class DatebaseLoggerProvider : ILoggerProvider
{
if (logDbContext == null)
{
lock (logDbContextLock)
lock (LogDbContextLock)
{
// prevent re-entry call
if (logDbContext == null)
@@ -34,7 +35,7 @@ public class DatebaseLoggerProvider : ILoggerProvider
logDbContext = LogDbContext.Create($"Data Source={myDocument.Locate("Log.db")}");
if (logDbContext.Database.GetPendingMigrations().Any())
{
Debug.WriteLine("Performing LogDbContext Migrations");
Debug.WriteLine("[Debug] Performing LogDbContext Migrations");
logDbContext.Database.Migrate();
}
@@ -51,11 +52,12 @@ public class DatebaseLoggerProvider : ILoggerProvider
/// <inheritdoc/>
public ILogger CreateLogger(string name)
{
return new DatebaseLogger(name, LogDbContext, logDbContextLock);
return new DatebaseLogger(name, LogDbContext, LogDbContextLock);
}
/// <inheritdoc/>
public void Dispose()
{
LogDbContext.Dispose();
}
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Extension;
/// <summary>
/// 信号量扩展
/// </summary>
public static class SemaphoreSlimExtensions
{
/// <summary>
/// 异步进入信号量
/// </summary>
/// <param name="semaphoreSlim">信号量</param>
/// <returns>可释放的对象,用于释放信号量</returns>
public static async Task<IDisposable> EnterAsync(this SemaphoreSlim semaphoreSlim)
{
await semaphoreSlim.WaitAsync();
return new SemaphoreSlimReleaser(semaphoreSlim);
}
private struct SemaphoreSlimReleaser : IDisposable
{
private readonly SemaphoreSlim semaphoreSlim;
public SemaphoreSlimReleaser(SemaphoreSlim semaphoreSlim)
{
this.semaphoreSlim = semaphoreSlim;
}
public void Dispose()
{
semaphoreSlim.Release();
}
}
}

View File

@@ -51,7 +51,7 @@ internal static class IocConfiguration
{
if (context.Database.GetPendingMigrations().Any())
{
Debug.WriteLine("Performing AppDbContext Migrations");
Debug.WriteLine("[Debug] Performing AppDbContext Migrations");
context.Database.Migrate();
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Web.Enka;
using Snap.Hutao.Web.Hoyolab.Bbs.User;
@@ -29,7 +30,7 @@ internal static class IocHttpClientConfiguration
{
// services
services.AddHttpClient<MetadataService>(DefaultConfiguration);
// services.AddHttpClient<ImageCache>(DefaultConfiguration);
services.AddHttpClient<ImageCache>(DefaultConfiguration).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { MaxConnectionsPerServer = 20 });
// normal clients
services.AddHttpClient<AnnouncementClient>(DefaultConfiguration);

View File

@@ -7,7 +7,6 @@ using Snap.Hutao.Control.HostBackdrop;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Win32;
using System.Runtime.InteropServices;
using WinRT.Interop;
namespace Snap.Hutao;

View File

@@ -1,4 +1,7 @@
namespace Snap.Hutao.Model.Metadata.Annotation;
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata.Annotation;
/// <summary>
/// 格式化方法

View File

@@ -13,10 +13,10 @@ public class DescParam
/// <summary>
/// 描述
/// </summary>
public IEnumerable<string> Descriptions { get; set; } = default!;
public IList<string> Descriptions { get; set; } = default!;
/// <summary>
/// 参数
/// </summary>
public IEnumerable<LevelParam<int, double>> Parameters { get; set; } = default!;
}
public IList<LevelParam<int, double>> Parameters { get; set; } = default!;
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.Model.Metadata.Avatar;
@@ -13,7 +14,7 @@ public class SkillDepot
/// <summary>
/// 技能天赋
/// </summary>
public IEnumerable<ProudableSkill> Skills { get; set; } = default!;
public IList<ProudableSkill> Skills { get; set; } = default!;
/// <summary>
/// 大招
@@ -23,10 +24,30 @@ public class SkillDepot
/// <summary>
/// 固有天赋
/// </summary>
public IEnumerable<ProudableSkill> Inherents { get; set; } = default!;
public IList<ProudableSkill> Inherents { get; set; } = default!;
/// <summary>
/// 全部天赋
/// </summary>
public IList<ProudableSkill> CompositeSkills => GetCompositeSkills().ToList();
/// <summary>
/// 命之座
/// </summary>
public IEnumerable<SkillBase> Talents { get; set; } = default!;
public IList<SkillBase> Talents { get; set; } = default!;
private IEnumerable<ProudableSkill> GetCompositeSkills()
{
foreach (ProudableSkill skill in Skills)
{
yield return skill;
}
yield return EnergySkill;
foreach (ProudableSkill skill in Inherents)
{
yield return skill;
}
}
}

View File

@@ -23,4 +23,4 @@ internal class AvatarIconConverter : IValueConverter
{
throw Must.NeverHappen();
}
}
}

View File

@@ -17,25 +17,26 @@ internal class DescParamDescriptor : IValueConverter
/// <inheritdoc/>
public object Convert(object value, Type targetType, object parameter, string language)
{
DescParam descParam = (DescParam)value;
IEnumerable<DescFormat> parsedDescriptions = descParam.Descriptions.Select(desc =>
{
string[] parts = desc.Split('|', 2);
return new DescFormat(parts[0], parts[1]);
});
DescParam rawDescParam = (DescParam)value;
IList<IList<string>> parameters = descParam.Parameters
.Select(param =>
// Spilt rawDesc into two parts: desc and format
IList<DescFormat> parsedDescriptions = rawDescParam.Descriptions
.Select(desc =>
{
IList<string> parameters = GetFormattedParameters(parsedDescriptions, param.Parameters);
parameters.Insert(0, param.Level.ToString());
return parameters;
string[] parts = desc.Split('|', 2);
return new DescFormat(parts[0], parts[1]);
})
.ToList();
List<string> descList = parsedDescriptions.Select(p => p.Description).ToList();
descList.Insert(0, "等级");
return new DescParamInternal(descList, parameters);
IList<LevelParam<string, ParameterInfo>> parameters = rawDescParam.Parameters
.Select(param =>
{
IList<ParameterInfo> parameters = GetFormattedParameters(parsedDescriptions, param.Parameters);
return new LevelParam<string, ParameterInfo>() { Level = param.Level.ToString(), Parameters = parameters };
})
.ToList();
return parameters;
}
/// <inheritdoc/>
@@ -44,6 +45,22 @@ internal class DescParamDescriptor : IValueConverter
throw Must.NeverHappen();
}
private static IList<ParameterInfo> GetFormattedParameters(IList<DescFormat> formats, IList<double> param)
{
List<ParameterInfo> results = new();
for (int index = 0; index < formats.Count; index++)
{
DescFormat descFormat = formats[index];
string format = descFormat.Format;
string resultFormatted = Regex.Replace(format, @"{param\d+.*?}", match => EvaluateMatch(match, param));
results.Add(new ParameterInfo { Description = descFormat.Description, Parameter = resultFormatted });
}
return results;
}
private static string EvaluateMatch(Match match, IList<double> param)
{
if (match.Success)
@@ -74,19 +91,6 @@ internal class DescParamDescriptor : IValueConverter
}
}
private IList<string> GetFormattedParameters(IEnumerable<DescFormat> formats, IList<double> param)
{
List<string> results = new();
foreach (DescFormat descFormat in formats)
{
string format = descFormat.Format;
string resultFormatted = Regex.Replace(format, @"{param\d+.*?}", match => EvaluateMatch(match, param));
results.Add(resultFormatted);
}
return results;
}
private class DescFormat
{
public DescFormat(string description, string format)
@@ -99,17 +103,4 @@ internal class DescParamDescriptor : IValueConverter
public string Format { get; set; }
}
private class DescParamInternal
{
public DescParamInternal(IList<string> descriptions, IList<IList<string>> parameters)
{
Descriptions = descriptions;
Parameters = parameters;
}
public IList<string> Descriptions { get; set; }
public IList<IList<string>> Parameters { get; set; }
}
}
}

View File

@@ -1,26 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Data;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Converter;
/// <summary>
/// 战斗属性转换器
/// </summary>
internal class FightPropertyConverter : IValueConverter
{
/// <inheritdoc/>
public object Convert(object value, Type targetType, object parameter, string language)
{
return ((FightProperty)value).GetDescription();
}
/// <inheritdoc/>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw Must.NeverHappen();
}
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Data;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Annotation;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.Model.Metadata.Converter;
/// <summary>
/// 基础属性翻译器
/// </summary>
internal class PropertyInfoDescriptor : IValueConverter
{
/// <inheritdoc/>
public object Convert(object value, Type targetType, object parameter, string language)
{
PropertyInfo rawDescParam = (PropertyInfo)value;
IList<LevelParam<string, ParameterInfo>> parameters = rawDescParam.Parameters
.Select(param =>
{
IList<ParameterInfo> parameters = GetFormattedParameters(param.Parameters, rawDescParam.Properties);
return new LevelParam<string, ParameterInfo>() { Level = param.Level, Parameters = parameters };
})
.ToList();
return parameters;
}
/// <inheritdoc/>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw Must.NeverHappen();
}
private static IList<ParameterInfo> GetFormattedParameters(IList<double> parameters, IList<FightProperty> properties)
{
List<ParameterInfo> results = new();
for (int index = 0; index < parameters.Count; index++)
{
double param = parameters[index];
FormatMethod method = properties[index].GetFormat();
string valueFormatted = method switch
{
FormatMethod.Integer => Math.Round((double)param, MidpointRounding.AwayFromZero).ToString(),
FormatMethod.Percent => string.Format("{0:P1}", param),
_ => param.ToString(),
};
results.Add(new ParameterInfo { Description = properties[index].GetDescription(), Parameter = valueFormatted });
}
return results;
}
}

View File

@@ -2,27 +2,30 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Data;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Annotation;
namespace Snap.Hutao.Model.Metadata.Converter;
/// <summary>
/// 战斗属性数值格式化
/// 技能图标转换
/// </summary>
internal class FightPropertyValueFormatter : IValueConverter
internal class SkillIconConverter : IValueConverter
{
private const string SkillUrl = "https://static.snapgenshin.com/Skill/{0}.png";
private const string TalentUrl = "https://static.snapgenshin.com/Talent/{0}.png";
/// <inheritdoc/>
public object Convert(object value, Type targetType, object parameter, string language)
{
FormatMethod method = ((FightProperty)parameter).GetFormat();
string target = (string)value;
return method switch
if (target.StartsWith("UI_Talent_"))
{
FormatMethod.Integer => Math.Round((double)value, MidpointRounding.AwayFromZero),
FormatMethod.Percent => string.Format("{0:P1}", value),
_ => value,
};
return new Uri(string.Format(TalentUrl, target));
}
else
{
return new Uri(string.Format(SkillUrl, target));
}
}
/// <inheritdoc/>

View File

@@ -0,0 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata;
/// <summary>
/// 包装一个描述参数
/// 专用于绑定
/// </summary>
public class ParameterInfo
{
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 参数
/// </summary>
public string Parameter { get; set; } = default!;
}

View File

@@ -14,10 +14,10 @@ public class PropertyInfo
/// <summary>
/// 提升的属性
/// </summary>
public IEnumerable<FightProperty> Properties { get; set; } = default!;
public IList<FightProperty> Properties { get; set; } = default!;
/// <summary>
/// 参数
/// </summary>
public IEnumerable<LevelParam<string, double>> Parameters { get; set; } = default!;
public IList<LevelParam<string, double>> Parameters { get; set; } = default!;
}

View File

@@ -9,7 +9,7 @@
<Identity
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
Publisher="CN=DGP Studio"
Version="1.0.16.0" />
Version="1.0.18.0" />
<Properties>
<DisplayName>胡桃</DisplayName>

View File

@@ -1,7 +1,8 @@
{
"profiles": {
"Snap.Hutao (Package)": {
"commandName": "MsixPackage"
"commandName": "MsixPackage",
"nativeDebugging": false
},
"Snap.Hutao (Unpackaged)": {
"commandName": "Project"

View File

@@ -34,7 +34,9 @@
<None Remove="Resource\Icon\UI_Icon_Achievement.png" />
<None Remove="Resource\Segoe Fluent Icons.ttf" />
<None Remove="stylecop.json" />
<None Remove="View\Control\DescParamComboBox.xaml" />
<None Remove="View\Control\ItemIcon.xaml" />
<None Remove="View\Control\SkillPivot.xaml" />
<None Remove="View\Dialog\UserDialog.xaml" />
<None Remove="View\MainView.xaml" />
<None Remove="View\Page\AchievementPage.xaml" />
@@ -104,6 +106,16 @@
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<Page Update="View\Control\DescParamComboBox.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Control\SkillPivot.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\UserView.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -0,0 +1,45 @@
<UserControl
x:Class="Snap.Hutao.View.Control.DescParamComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sc="using:SettingsUI.Controls"
mc:Ignorable="d">
<UserControl.Resources>
<Style TargetType="ComboBox" BasedOn="{StaticResource DefaultComboBoxStyle}">
<Setter Property="MinWidth" Value="120"/>
</Style>
</UserControl.Resources>
<StackPanel>
<sc:SettingsGroup VerticalAlignment="Top" Margin="0,-64,0,0">
<sc:Setting Header="等级" Padding="12,0,6,0">
<sc:Setting.ActionContent>
<ComboBox
x:Name="ItemHost"
SelectionChanged="ItemHostSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Level}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</sc:Setting.ActionContent>
</sc:Setting>
</sc:SettingsGroup>
<ItemsControl
x:Name="DetailsHost"
VerticalAlignment="Top">
<ItemsControl.ItemTemplate>
<DataTemplate>
<sc:Setting Margin="0,2,0,0" Header="{Binding Description}" Padding="12,0">
<sc:Setting.ActionContent>
<TextBlock Text="{Binding Parameter}"/>
</sc:Setting.ActionContent>
</sc:Setting>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,61 @@
// 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;
using Snap.Hutao.Model.Metadata;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.View.Control;
/// <summary>
/// 描述参数组合框
/// </summary>
public sealed partial class DescParamComboBox : UserControl
{
private static readonly DependencyProperty SourceProperty = Property<DescParamComboBox>
.Depend<IList<LevelParam<string, ParameterInfo>>>(nameof(Source), default!, OnSourceChanged);
/// <summary>
/// 构造一个新的描述参数组合框
/// </summary>
public DescParamComboBox()
{
InitializeComponent();
}
/// <summary>
/// 技能列表
/// </summary>
public IList<LevelParam<string, ParameterInfo>> Source
{
get { return (IList<LevelParam<string, ParameterInfo>>)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
// Some of the {x:Bind} feature is not working properly,
// so we use this simple code behind approach to achieve selection function
if (sender is DescParamComboBox descParamComboBox)
{
if (args.NewValue != args.OldValue && args.NewValue is IList<LevelParam<string, ParameterInfo>> list)
{
descParamComboBox.ItemHost.ItemsSource = list;
descParamComboBox.ItemHost.SelectedIndex = 0;
descParamComboBox.DetailsHost.ItemsSource = list.FirstOrDefault()?.Parameters;
}
}
}
private void ItemHostSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ComboBox comboBox && comboBox.SelectedIndex >= 0)
{
DetailsHost.ItemsSource = Source[comboBox.SelectedIndex]?.Parameters;
}
}
}

View File

@@ -0,0 +1,48 @@
<UserControl
x:Class="Snap.Hutao.View.Control.SkillPivot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shct="using:Snap.Hutao.Control.Text"
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
mc:Ignorable="d">
<UserControl.Resources>
<shmmc:SkillIconConverter x:Key="SkillIconConverter"/>
<shmmc:DescParamDescriptor x:Key="DescParamDescriptor"/>
<Thickness x:Key="PivotHeaderItemMargin">0,0,16,0</Thickness>
<Thickness x:Key="PivotItemMargin">0</Thickness>
<Style TargetType="PivotHeaderItem" BasedOn="{StaticResource DefaultPivotHeaderItemStyle}">
<Setter Property="Height" Value="80"/>
</Style>
</UserControl.Resources>
<Pivot
x:Name="ItemHost"
ItemsSource="{x:Bind Skills,Mode=OneWay}"
SelectedItem="{x:Bind Selected}"
ItemTemplate="{x:Bind ItemTemplate}"
Style="{StaticResource DefaultPivotStyle}"
>
<Pivot.HeaderTemplate>
<DataTemplate>
<StackPanel>
<shci:MonoChrome
Width="36"
Height="36"
Source="{Binding Icon,Converter={StaticResource SkillIconConverter}}"/>
<TextBlock
Margin="0,8,0,0"
Text="{Binding Name}"
HorizontalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"/>
</StackPanel>
</DataTemplate>
</Pivot.HeaderTemplate>
</Pivot>
</UserControl>

View File

@@ -0,0 +1,54 @@
// 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;
using System.Collections;
namespace Snap.Hutao.View.Control;
/// <summary>
/// 技能展柜
/// </summary>
public sealed partial class SkillPivot : UserControl
{
private static readonly DependencyProperty SkillsProperty = Property<SkillPivot>.Depend<IList>(nameof(Skills));
private static readonly DependencyProperty SelectedProperty = Property<SkillPivot>.Depend<object>(nameof(Selected));
private static readonly DependencyProperty ItemTemplateProperty = Property<SkillPivot>.Depend<DataTemplate>(nameof(ItemTemplate));
/// <summary>
/// 创建一个新的技能展柜
/// </summary>
public SkillPivot()
{
InitializeComponent();
}
/// <summary>
/// 技能列表
/// </summary>
public IList Skills
{
get { return (IList)GetValue(SkillsProperty); }
set { SetValue(SkillsProperty, value); }
}
/// <summary>
/// 选中的项
/// </summary>
public object Selected
{
get { return GetValue(SelectedProperty); }
set { SetValue(SelectedProperty, value); }
}
/// <summary>
/// 项目模板
/// </summary>
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
}

View File

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

View File

@@ -4,9 +4,10 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.VisualStudio.Threading;
using Snap.Hutao.Core;
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Navigation;
using Windows.System;
namespace Snap.Hutao.View.Page;
@@ -55,31 +56,7 @@ openInWebview: function(url){ location.href = url }}";
}
}
private async Task LoadAnnouncementAsync(INavigationData data)
{
try
{
await WebView.EnsureCoreWebView2Async();
await WebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(MihoyoSDKDefinition);
WebView.CoreWebView2.WebMessageReceived += (_, e) => Browser.Open(e.TryGetWebMessageAsString());
}
catch (Exception ex)
{
data.NotifyNavigationException(ex);
return;
}
WebView.NavigateToString(ReplaceForeground(targetContent, ActualTheme));
data.NotifyNavigationCompleted();
}
private void PageActualThemeChanged(FrameworkElement sender, object args)
{
WebView.NavigateToString(ReplaceForeground(targetContent, ActualTheme));
}
private string? ReplaceForeground(string? rawContent, ElementTheme theme)
private static string? ReplaceForeground(string? rawContent, ElementTheme theme)
{
if (string.IsNullOrWhiteSpace(rawContent))
{
@@ -105,4 +82,39 @@ openInWebview: function(url){ location.href = url }}";
// wrap a default color body around
return $@"<body style=""{(isDarkMode ? LightColor1 : DarkColor1)}"">{rawContent}</body>";
}
private async Task LoadAnnouncementAsync(INavigationData data)
{
try
{
await WebView.EnsureCoreWebView2Async();
await WebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(MihoyoSDKDefinition);
WebView.CoreWebView2.WebMessageReceived += OnWebMessageReceived;
}
catch (Exception ex)
{
data.NotifyNavigationException(ex);
return;
}
WebView.NavigateToString(ReplaceForeground(targetContent, ActualTheme));
data.NotifyNavigationCompleted();
}
private void PageActualThemeChanged(FrameworkElement sender, object args)
{
WebView.NavigateToString(ReplaceForeground(targetContent, ActualTheme));
}
[SuppressMessage("", "VSTHRD100")]
private async void OnWebMessageReceived(CoreWebView2 coreWebView2, CoreWebView2WebMessageReceivedEventArgs args)
{
string url = args.TryGetWebMessageAsString();
if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out Uri? uri))
{
await Launcher.LaunchUriAsync(uri).AsTask().ConfigureAwait(false);
}
}
}

View File

@@ -6,7 +6,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shct="using:Snap.Hutao.Control.Text"
@@ -26,30 +25,14 @@
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
<shmmc:FightPropertyConverter x:Key="FightPropertyConverter"/>
<shmmc:FightPropertyValueFormatter x:Key="FightPropertyValueFormatter"/>
<shmmc:DescParamDescriptor x:Key="DescParamDescriptor"/>
<shc:BindingProxy
x:Key="FightPropertyBindingProxy"
DataContext="{Binding Selected.Property.Properties}"/>
<shmmc:PropertyInfoDescriptor x:Key="PropertyDescriptor"/>
<DataTemplate x:Key="SkillDataTemplate">
<Expander
Margin="16,16,0,0"
Header="{Binding Name}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Margin="0,12,0,0">
<StackPanel Grid.Column="0">
<shct:DescriptionTextBlock
Grid.Column="0"
MaxWidth="280"
Margin="16"
Margin="0,0,0,16"
Description="{Binding Description}">
<shct:DescriptionTextBlock.Resources>
<Style
@@ -59,87 +42,33 @@
</Style>
</shct:DescriptionTextBlock.Resources>
</shct:DescriptionTextBlock>
<ScrollViewer
Grid.Column="1"
VerticalScrollMode="Disabled"
HorizontalScrollBarVisibility="Auto">
<Grid
Margin="16"
DataContext="{Binding Proud,Converter={StaticResource DescParamDescriptor}}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsControl
Grid.Row="0"
ItemsSource="{Binding Descriptions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<cwuc:UniformGrid
ColumnSpacing="16"
Columns="{Binding Descriptions.Count}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding}"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis"
Style="{StaticResource BaseTextBlockStyle}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<shvc:DescParamComboBox
Grid.Column="0"
HorizontalAlignment="Stretch"
Source="{Binding Proud,Converter={StaticResource DescParamDescriptor}}"/>
<ItemsControl
Margin="0,16,0,0"
Grid.Row="2"
ItemsSource="{Binding Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<cwuc:UniformGrid
ColumnSpacing="16"
Columns="{Binding Count}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding}"
TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ScrollViewer>
</Grid>
</Expander>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="InherentDataTemplate">
<Expander
Margin="16,16,0,0"
Header="{Binding Name}"
<DataTemplate x:Key="PropertyDataTemplate">
<shvc:DescParamComboBox
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch">
<shct:DescriptionTextBlock
Margin="16"
Description="{Binding Description}">
<shct:DescriptionTextBlock.Resources>
<Style
TargetType="TextBlock"
BasedOn="{StaticResource BodyTextBlockStyle}">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</shct:DescriptionTextBlock.Resources>
</shct:DescriptionTextBlock>
</Expander>
Source="{Binding Property,Converter={StaticResource PropertyDescriptor}}"/>
</DataTemplate>
<DataTemplate x:Key="TalentDataTemplate">
<shct:DescriptionTextBlock
Margin="0,12,0,0"
Description="{Binding Description}">
<shct:DescriptionTextBlock.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource BodyTextBlockStyle}">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</shct:DescriptionTextBlock.Resources>
</shct:DescriptionTextBlock>
</DataTemplate>
</Page.Resources>
@@ -180,12 +109,13 @@
</SplitView.Pane>
<SplitView.Content>
<Grid>
<!--渐变背景-->
<shci:Gradient
VerticalAlignment="Top"
Source="{Binding Selected,Converter={StaticResource AvatarNameCardPicConverter}}"/>
<ScrollViewer>
<StackPanel Margin="0,0,16,16">
<StackPanel Margin="0,0,20,16">
<!--简介-->
<Grid
Margin="16,16,0,16"
@@ -321,111 +251,34 @@
</StackPanel>
</Grid>
<!--属性-->
<Expander
Margin="16,0,0,0"
Header="基础数值"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch">
<ScrollViewer
VerticalScrollMode="Disabled"
HorizontalScrollBarVisibility="Auto">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<cwuc:UniformGrid
Grid.Row="0"
Columns="5"
ColumnSpacing="16"
DataContext="{Binding Selected.Property}">
<TextBlock
Style="{StaticResource BaseTextBlockStyle}"
TextWrapping="NoWrap"
Text="等级"/>
<TextBlock
TextWrapping="NoWrap"
Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding Properties[0], Converter={StaticResource FightPropertyConverter}}"/>
<TextBlock
TextWrapping="NoWrap"
Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding Properties[1], Converter={StaticResource FightPropertyConverter}}"/>
<TextBlock
TextWrapping="NoWrap"
Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding Properties[2], Converter={StaticResource FightPropertyConverter}}"/>
<TextBlock
TextWrapping="NoWrap"
Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding Properties[3], Converter={StaticResource FightPropertyConverter}}"/>
</cwuc:UniformGrid>
<ItemsControl
Margin="0,16,0,0"
Grid.Row="1"
ItemsSource="{Binding Selected.Property.Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<cwuc:UniformGrid
Columns="5"
ColumnSpacing="16">
<TextBlock
Text="{Binding Level}"/>
<TextBlock
Text="{Binding Parameters[0],
Converter={StaticResource FightPropertyValueFormatter},
ConverterParameter={Binding DataContext[0],Source={StaticResource FightPropertyBindingProxy}}}"/>
<TextBlock
Text="{Binding Parameters[1],
Converter={StaticResource FightPropertyValueFormatter},
ConverterParameter={Binding DataContext[1],Source={StaticResource FightPropertyBindingProxy}}}"/>
<TextBlock
Text="{Binding Parameters[2],
Converter={StaticResource FightPropertyValueFormatter},
ConverterParameter={Binding DataContext[2],Source={StaticResource FightPropertyBindingProxy}}}"/>
<TextBlock
Text="{Binding Parameters[3],
Converter={StaticResource FightPropertyValueFormatter},
ConverterParameter={Binding DataContext[3],Source={StaticResource FightPropertyBindingProxy}}}"/>
</cwuc:UniformGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ScrollViewer>
</Expander>
<TextBlock Text="天赋" Style="{StaticResource BaseTextBlockStyle}" Margin="16,16,0,0"/>
<ItemsControl
ItemsSource="{Binding Selected.SkillDepot.Skills}"
ItemTemplate="{StaticResource SkillDataTemplate}"/>
<ContentControl
Margin="16,16,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Content="{Binding Selected.SkillDepot.EnergySkill}"
ContentTemplate="{StaticResource SkillDataTemplate}"/>
Content="{Binding Selected,Mode=OneWay}"
ContentTemplate="{StaticResource PropertyDataTemplate}"/>
<ItemsControl
ItemsSource="{Binding Selected.SkillDepot.Inherents}"
ItemTemplate="{StaticResource InherentDataTemplate}"/>
<TextBlock Text="天赋" Style="{StaticResource BaseTextBlockStyle}" Margin="16,32,0,0"/>
<shvc:SkillPivot
Margin="16,16,0,0"
Skills="{Binding Selected.SkillDepot.CompositeSkills}"
ItemTemplate="{StaticResource SkillDataTemplate}"/>
<TextBlock Text="命之座" Style="{StaticResource BaseTextBlockStyle}" Margin="16,16,0,0"/>
<TextBlock Text="命之座" Style="{StaticResource BaseTextBlockStyle}" Margin="16,32,0,0"/>
<shvc:SkillPivot
Grid.Column="1"
Margin="16,16,0,0"
Skills="{Binding Selected.SkillDepot.Talents}"
ItemTemplate="{StaticResource TalentDataTemplate}"/>
<ItemsControl
ItemsSource="{Binding Selected.SkillDepot.Talents}"
ItemTemplate="{StaticResource InherentDataTemplate}"/>
<TextBlock Text="其他" Style="{StaticResource BaseTextBlockStyle}" Margin="16,16,0,0"/>
<TextBlock Text="其他" Style="{StaticResource BaseTextBlockStyle}" Margin="16,32,0,0"/>
<!--衣装-->
<Expander
Margin="16,16,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Header="衣装">
<ItemsControl
ItemsSource="{Binding Selected.Costumes}">
<ItemsControl ItemsSource="{Binding Selected.Costumes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,-4">
@@ -448,8 +301,7 @@
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Header="资料">
<ItemsControl
ItemsSource="{Binding Selected.FetterInfo.Fetters}">
<ItemsControl ItemsSource="{Binding Selected.FetterInfo.Fetters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,-4">
@@ -460,9 +312,7 @@
Margin="16,8,16,16"
Description="{Binding Context}">
<shct:DescriptionTextBlock.Resources>
<Style
TargetType="TextBlock"
BasedOn="{StaticResource CaptionTextBlockStyle}"/>
<Style TargetType="TextBlock" BasedOn="{StaticResource CaptionTextBlockStyle}"/>
</shct:DescriptionTextBlock.Resources>
</shct:DescriptionTextBlock>
<MenuFlyoutSeparator Margin="16,0"/>
@@ -473,12 +323,12 @@
</Expander>
<!--故事-->
<Expander
Margin="16,16,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Header="故事">
Margin="16,16,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Header="故事">
<ItemsControl
ItemsSource="{Binding Selected.FetterInfo.FetterStories}">
ItemsSource="{Binding Selected.FetterInfo.FetterStories}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,-4">

View File

@@ -14,34 +14,10 @@
<mxic:InvokeCommandAction Command="{Binding OpenUICommand}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
<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="6,2"
Height="36"
Width="36"/>
<TextBlock
VerticalAlignment="Center"
Margin="0,0,0,2"
Grid.Column="1"
Text="{Binding SelectedUser.UserInfo.Nickname,Mode=OneWay}"
TextTrimming="CharacterEllipsis"/>
<StackPanel>
<Button
Background="Transparent"
BorderBrush="{x:Null}"
Height="38.4"
Content="&#xE712;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
Grid.Column="2"
Margin="4">
<Button.Resources>
@@ -49,9 +25,36 @@
x:Key="ViewModelBindingProxy"
DataContext="{Binding}"/>
</Button.Resources>
<Button.Content>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<PersonPicture
ProfilePicture="{Binding SelectedUser.UserInfo.AvatarUrl,Mode=OneWay}"
HorizontalAlignment="Left"
Margin="1,1,6,1"
Height="36"
Width="36"/>
<TextBlock
VerticalAlignment="Center"
Margin="0,0,0,2"
Grid.Column="1"
Text="{Binding SelectedUser.UserInfo.Nickname,Mode=OneWay}"
TextTrimming="CharacterEllipsis"/>
<FontIcon
Grid.Column="2"
Glyph="&#xE76C;"
FontSize="12"
VerticalAlignment="Center"
Margin="0,0,8,0"/>
</Grid>
</Button.Content>
<Button.Flyout>
<Flyout
Placement="TopEdgeAlignedRight"
Placement="LeftEdgeAlignedBottom"
LightDismissOverlayMode="On">
<Flyout.FlyoutPresenterStyle>
<Style
@@ -173,7 +176,7 @@
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="PointerEntered">
<mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelVisibleStoryboard}"/>
@@ -183,7 +186,7 @@
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
@@ -196,11 +199,14 @@
</StackPanel>
</Flyout>
</Button.Flyout>
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}">
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</Button.Style>
</Button>
<NavigationViewItemSeparator
Margin="0,4,0,0"
Grid.Row="1"
Grid.ColumnSpan="3"
VerticalAlignment="Bottom"/>
</Grid>
<NavigationViewItemSeparator/>
</StackPanel>
</UserControl>

View File

@@ -34,14 +34,14 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
/// </summary>
public ICommand OpenDataFolderCommand { get; }
private Task OpenCacheFolderAsync()
private Task OpenCacheFolderAsync(CancellationToken token)
{
return Launcher.LaunchFolderAsync(App.AppData.TemporaryFolder).AsTask();
return Launcher.LaunchFolderAsync(App.Current.AppData.TemporaryFolder).AsTask(token);
}
private async Task OpenDataFolderAsync()
private async Task OpenDataFolderAsync(CancellationToken token)
{
StorageFolder folder = await KnownFolders.DocumentsLibrary.GetFolderAsync("Hutao").AsTask().ConfigureAwait(false);
await Launcher.LaunchFolderAsync(folder).AsTask().ConfigureAwait(false);
StorageFolder folder = await KnownFolders.DocumentsLibrary.GetFolderAsync("Hutao").AsTask(token).ConfigureAwait(false);
await Launcher.LaunchFolderAsync(folder).AsTask(token).ConfigureAwait(false);
}
}