mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Merge pull request #1131 from DGP-Studio/develop
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" Version="3.3.4" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Test.BaseClassLibrary;
|
||||
|
||||
[TestClass]
|
||||
public class CollectionsMarshalTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void DictionaryMarshalGetValueRefOrNullRefIsNullRef()
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
Dictionary<uint, string> dictionaryValueKeyRefValue = [];
|
||||
Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
|
||||
Dictionary<string, uint> dictionaryRefKeyValueValue = [];
|
||||
Dictionary<string, string> dictionaryRefKeyRefValue = [];
|
||||
#else
|
||||
Dictionary<uint, string> dictionaryValueKeyRefValue = new();
|
||||
Dictionary<uint, uint> dictionaryValueKeyValueValue = new();
|
||||
Dictionary<string, uint> dictionaryRefKeyValueValue = new();
|
||||
Dictionary<string, string> dictionaryRefKeyRefValue = new();
|
||||
#endif
|
||||
|
||||
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyRefValue, 1U)));
|
||||
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyValueValue, 1U)));
|
||||
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryRefKeyValueValue, "no such key")));
|
||||
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryRefKeyRefValue, "no such key")));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DictionaryMarshalGetValueRefOrAddDefaultIsDefault()
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
Dictionary<uint, string> dictionaryValueKeyRefValue = [];
|
||||
Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
|
||||
Dictionary<string, uint> dictionaryRefKeyValueValue = [];
|
||||
Dictionary<string, string> dictionaryRefKeyRefValue = [];
|
||||
#else
|
||||
Dictionary<uint, string> dictionaryValueKeyRefValue = new();
|
||||
Dictionary<uint, uint> dictionaryValueKeyValueValue = new();
|
||||
Dictionary<string, uint> dictionaryRefKeyValueValue = new();
|
||||
Dictionary<string, string> dictionaryRefKeyRefValue = new();
|
||||
#endif
|
||||
|
||||
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyRefValue, 1U, out _) == default);
|
||||
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyValueValue, 1U, out _) == default);
|
||||
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryRefKeyValueValue, "no such key", out _) == default);
|
||||
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryRefKeyRefValue, "no such key", out _) == default);
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,7 @@ namespace Snap.Hutao.Test.BaseClassLibrary;
|
||||
[TestClass]
|
||||
public class JsonSerializeTest
|
||||
{
|
||||
private TestContext? testContext;
|
||||
|
||||
public TestContext? TestContext { get => testContext; set => testContext = value; }
|
||||
public TestContext? TestContext { get; set; }
|
||||
|
||||
private readonly JsonSerializerOptions AlowStringNumberOptions = new()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Test.IncomingFeature;
|
||||
|
||||
[TestClass]
|
||||
public sealed class GeniusInvokationDecoding
|
||||
{
|
||||
public TestContext? TestContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// https://www.bilibili.com/video/av278125720
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public unsafe void GeniusInvokationShareCodeDecoding()
|
||||
{
|
||||
// 51 bytes obfuscated data
|
||||
byte[] bytes = Convert.FromBase64String("BCHBwxQNAYERyVANCJGBynkOCZER2pgOCrFx8poQChGR9bYQDEGB9rkQDFKRD7oRDeEB");
|
||||
|
||||
// ---------------------------------------------
|
||||
// | Data | Caesar Cipher Key |
|
||||
// |----------|-------------------|
|
||||
// | 50 Bytes | 1 Byte |
|
||||
// ---------------------------------------------
|
||||
// Data:
|
||||
// 00000100 00100001 11000001 11000011 00010100
|
||||
// 00001101 00000001 10000001 00010001 11001001
|
||||
// 01010000 00001101 00001000 10010001 10000001
|
||||
// 11001010 01111001 00001110 00001001 10010001
|
||||
// 00010001 11011010 10011000 00001110 00001010
|
||||
// 10110001 01110001 11110010 10011010 00010000
|
||||
// 00001010 00010001 10010001 11110101 10110110
|
||||
// 00010000 00001100 01000001 10000001 11110110
|
||||
// 10111001 00010000 00001100 01010010 10010001
|
||||
// 00001111 10111010 00010001 00001101 11100001
|
||||
// ---------------------------------------------
|
||||
// Caesar Cipher Key:
|
||||
// 00000001
|
||||
// ---------------------------------------------
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
// Reinterpret as 50 byte actual data and 1 deobfuscate key byte
|
||||
EncryptedDataAndKey* data = (EncryptedDataAndKey*)ptr;
|
||||
byte* dataPtr = data->Data;
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// | First | Second | Padding |
|
||||
// |-----------|----------|---------|
|
||||
// | 25 Bytes | 25 Bytes | 1 Byte |
|
||||
// ----------------------------------------------------------
|
||||
// We are doing two things here:
|
||||
// 1. Retrieve actual data by subtracting key
|
||||
// 2. Store data into two halves by alternating between them
|
||||
// ----------------------------------------------------------
|
||||
// What we will get after this step:
|
||||
// ----------------------------------------------------------
|
||||
// First:
|
||||
// 00000011 11000000 00010011 00000000 00010000
|
||||
// 01001111 00000111 10000000 01111000 00001000
|
||||
// 00010000 10010111 00001001 01110000 10011001
|
||||
// 00001001 10010000 10110101 00001011 10000000
|
||||
// 10111000 00001011 10010000 10111001 00001100
|
||||
// ----------------------------------------------------------
|
||||
// Second:
|
||||
// 00100000 11000010 00001100 10000000 11001000
|
||||
// 00001100 10010000 11001001 00001101 10010000
|
||||
// 11011001 00001101 10110000 11110001 00001111
|
||||
// 00010000 11110100 00001111 01000000 11110101
|
||||
// 00001111 01010001 00001110 00010000 11100000
|
||||
// ----------------------------------------------------------
|
||||
RearrangeBuffer rearranged = default;
|
||||
byte* pFirst = rearranged.First;
|
||||
byte* pSecond = rearranged.Second;
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
// Determine which half are we going to insert
|
||||
byte** ppTarget = i % 2 == 0 ? &pFirst : &pSecond;
|
||||
|
||||
// (actual data = data - key) and store it directly to the target half
|
||||
**ppTarget = unchecked((byte)(dataPtr[i] - data->Key));
|
||||
|
||||
(*ppTarget)++;
|
||||
}
|
||||
|
||||
// Prepare decoded data result storage
|
||||
DecryptedData decoded = default;
|
||||
ushort* pDecoded = decoded.Data;
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// | Data |
|
||||
// |----------| x 17 = 51 Bytes
|
||||
// | 3 Bytes |
|
||||
// ----------------------------------------------------------
|
||||
// Grouping each 3 bytes and read out as 2 ushort with
|
||||
// 12 bits each (Big Endian)
|
||||
// ----------------------------------------------------------
|
||||
// 00000011 1100·0000 00010011|
|
||||
// 00000000 0001·0000 01001111|
|
||||
// 00000111 1000·0000 01111000|
|
||||
// 00001000 0001·0000 10010111|
|
||||
// 00001001 0111·0000 10011001|
|
||||
// 00001001 1001·0000 10110101|
|
||||
// 00001011 1000·0000 10111000|
|
||||
// 00001011 1001·0000 10111001|
|
||||
// 00001100 0010·0000 11000010|
|
||||
// 00001100 1000·0000 11001000|
|
||||
// 00001100 1001·0000 11001001|
|
||||
// 00001101 1001·0000 11011001|
|
||||
// 00001101 1011·0000 11110001|
|
||||
// 00001111 0001·0000 11110100|
|
||||
// 00001111 0100·0000 11110101|
|
||||
// 00001111 0101·0001 00001110|
|
||||
// 00010000 1110·0000 -padding|[padding32]
|
||||
// ----------------------------------------------------------
|
||||
// reinterpret as DecodeGroupingHelper for each 3 bytes
|
||||
DecodeGroupingHelper* pGroup = (DecodeGroupingHelper*)&rearranged;
|
||||
for (int i = 0; i < 17; i++)
|
||||
{
|
||||
(ushort first, ushort second) = pGroup->GetData();
|
||||
|
||||
*pDecoded = first;
|
||||
*(pDecoded + 1) = second;
|
||||
|
||||
pDecoded += 2;
|
||||
pGroup++;
|
||||
}
|
||||
|
||||
// Now we get
|
||||
// 60, 19, 1,
|
||||
// 79,120,120,
|
||||
// 129,151,151,
|
||||
// 153,153,181,
|
||||
// 184,184,185,
|
||||
// 185,194,194,
|
||||
// 200,200,201,
|
||||
// 201,217,217,
|
||||
// 219,241,241,
|
||||
// 244,244,245,
|
||||
// 245,270,270,
|
||||
StringBuilder stringBuilder = new();
|
||||
for (int i = 0; i < 33; i++)
|
||||
{
|
||||
stringBuilder
|
||||
.AppendFormat(CultureInfo.InvariantCulture, "{0,3}", decoded.Data[i])
|
||||
.Append(',');
|
||||
|
||||
if (i % 11 == 10)
|
||||
{
|
||||
stringBuilder.Append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
TestContext?.WriteLine(stringBuilder.ToString(0, stringBuilder.Length - 1));
|
||||
|
||||
ushort[] resultArray = new ushort[33];
|
||||
Span<ushort> result = new((ushort*)&decoded, 33);
|
||||
result.CopyTo(resultArray);
|
||||
|
||||
ushort[] testKnownResult =
|
||||
#if NET8_0_OR_GREATER
|
||||
[
|
||||
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
|
||||
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
|
||||
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
|
||||
];
|
||||
#else
|
||||
{
|
||||
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
|
||||
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
|
||||
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
|
||||
};
|
||||
#endif
|
||||
|
||||
CollectionAssert.AreEqual(resultArray, testKnownResult);
|
||||
}
|
||||
}
|
||||
|
||||
private struct EncryptedDataAndKey
|
||||
{
|
||||
public unsafe fixed byte Data[50];
|
||||
public byte Key;
|
||||
}
|
||||
|
||||
private struct RearrangeBuffer
|
||||
{
|
||||
public unsafe fixed byte First[25];
|
||||
public unsafe fixed byte Second[25];
|
||||
|
||||
// Make it 51 bytes
|
||||
// allow to be group as 17 DecodeGroupingHelper later
|
||||
public byte padding;
|
||||
|
||||
// prevent accidently int32 cast access violation
|
||||
public byte paddingTo32;
|
||||
}
|
||||
|
||||
private struct DecodeGroupingHelper
|
||||
{
|
||||
public unsafe fixed byte Data[3];
|
||||
|
||||
public unsafe (ushort First, ushort Second) GetData()
|
||||
{
|
||||
fixed (byte* ptr = Data)
|
||||
{
|
||||
uint value = BinaryPrimitives.ReverseEndianness((*(uint*)ptr) & 0x00FFFFFF) >> 8; // keep low 24 bits only
|
||||
return ((ushort)((value >> 12) & 0x0FFF), (ushort)(value & 0x0FFF));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct DecryptedData
|
||||
{
|
||||
public unsafe fixed ushort Data[33];
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -4,12 +4,18 @@
|
||||
"add": {
|
||||
"extensionToExtension": {
|
||||
"add": {
|
||||
".json": [ ".txt" ]
|
||||
".json": [
|
||||
".txt"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pathSegment": {
|
||||
"add": {
|
||||
".*": [ ".cs", ".resx" ]
|
||||
".*": [
|
||||
".cs",
|
||||
".resx",
|
||||
".appxmanifest"
|
||||
]
|
||||
}
|
||||
},
|
||||
"fileSuffixToExtension": {
|
||||
@@ -19,12 +25,24 @@
|
||||
},
|
||||
"fileToFile": {
|
||||
"add": {
|
||||
".filenesting.json": [ "App.xaml.cs" ],
|
||||
"app.manifest": [ "App.xaml.cs" ],
|
||||
"Package.appxmanifest": [ "App.xaml" ],
|
||||
"Package.StoreAssociation.xml": [ "App.xaml" ],
|
||||
".editorconfig": [ "Program.cs" ],
|
||||
"GlobalUsing.cs": [ "Program.cs" ]
|
||||
".filenesting.json": [
|
||||
"App.xaml.cs"
|
||||
],
|
||||
"app.manifest": [
|
||||
"App.xaml.cs"
|
||||
],
|
||||
"Package.appxmanifest": [
|
||||
"App.xaml"
|
||||
],
|
||||
"Package.StoreAssociation.xml": [
|
||||
"App.xaml"
|
||||
],
|
||||
".editorconfig": [
|
||||
"Program.cs"
|
||||
],
|
||||
"GlobalUsing.cs": [
|
||||
"Program.cs"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<ResourceDictionary Source="ms-appx:///Control/Image/CachedImage.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Card.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Color.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ComboBox.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Converter.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/CornerRadius.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/FlyoutStyle.xaml"/>
|
||||
|
||||
@@ -30,7 +30,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
private readonly RoutedEventHandler unloadEventHandler;
|
||||
private readonly SizeChangedEventHandler sizeChangedEventHandler;
|
||||
private readonly TypedEventHandler<LoadedImageSurface, LoadedImageSourceLoadCompletedEventArgs> loadedImageSourceLoadCompletedEventHandler;
|
||||
|
||||
@@ -46,9 +45,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
serviceProvider = this.ServiceProvider();
|
||||
this.DisableInteraction();
|
||||
|
||||
unloadEventHandler = OnUnload;
|
||||
Unloaded += unloadEventHandler;
|
||||
|
||||
sizeChangedEventHandler = OnSizeChanged;
|
||||
SizeChanged += sizeChangedEventHandler;
|
||||
|
||||
@@ -67,10 +63,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void Unloading()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新视觉对象
|
||||
/// </summary>
|
||||
@@ -240,14 +232,4 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
UpdateVisual(spriteVisual);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUnload(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Unloading();
|
||||
spriteVisual?.Dispose();
|
||||
spriteVisual = null;
|
||||
|
||||
SizeChanged -= sizeChangedEventHandler;
|
||||
Unloaded -= unloadEventHandler;
|
||||
}
|
||||
}
|
||||
@@ -45,16 +45,6 @@ internal sealed class MonoChrome : CompositionImage
|
||||
return compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
|
||||
}
|
||||
|
||||
protected override void Unloading()
|
||||
{
|
||||
ActualThemeChanged -= actualThemeChangedEventHandler;
|
||||
|
||||
backgroundBrush?.Dispose();
|
||||
backgroundBrush = null;
|
||||
|
||||
base.Unloading();
|
||||
}
|
||||
|
||||
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
||||
{
|
||||
if (backgroundBrush is not null)
|
||||
|
||||
@@ -128,15 +128,8 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
|
||||
UniformStaggeredItem item = state.GetItemAt(i);
|
||||
if (item.Height == 0)
|
||||
{
|
||||
// https://github.com/DGP-Studio/Snap.Hutao/issues/1079
|
||||
// The first element must be force refreshed otherwise
|
||||
// it will use the old one realized
|
||||
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
|
||||
// Now we need to refresh the first element of each column
|
||||
ElementRealizationOptions options = i < numberOfColumns ? ElementRealizationOptions.ForceCreate : ElementRealizationOptions.None;
|
||||
|
||||
// Item has not been measured yet. Get the element and store the values
|
||||
UIElement element = context.GetOrCreateElementAt(i, options);
|
||||
UIElement element = context.GetOrCreateElementAt(i);
|
||||
element.Measure(new Size(state.ColumnWidth, availableHeight));
|
||||
item.Height = element.DesiredSize.Height;
|
||||
item.Element = element;
|
||||
@@ -209,11 +202,8 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
|
||||
for (int columnIndex = 0; columnIndex < state.NumberOfColumns; columnIndex++)
|
||||
{
|
||||
UniformStaggeredColumnLayout layout = state.GetColumnLayout(columnIndex);
|
||||
Span<UniformStaggeredItem> layoutSpan = CollectionsMarshal.AsSpan(layout);
|
||||
for (int i = 0; i < layoutSpan.Length; i++)
|
||||
foreach (ref readonly UniformStaggeredItem item in CollectionsMarshal.AsSpan(layout))
|
||||
{
|
||||
ref readonly UniformStaggeredItem item = ref layoutSpan[i];
|
||||
|
||||
double bottom = item.Top + item.Height;
|
||||
if (bottom < context.RealizationRect.Top)
|
||||
{
|
||||
|
||||
@@ -62,11 +62,9 @@ internal sealed class UniformStaggeredLayoutState
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
internal UniformStaggeredColumnLayout GetColumnLayout(int columnIndex)
|
||||
{
|
||||
this.columnLayout.TryGetValue(columnIndex, out UniformStaggeredColumnLayout? columnLayout);
|
||||
return columnLayout!;
|
||||
return columnLayout[columnIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,6 +72,21 @@ internal sealed class UniformStaggeredLayoutState
|
||||
/// </summary>
|
||||
internal void Clear()
|
||||
{
|
||||
// https://github.com/DGP-Studio/Snap.Hutao/issues/1079
|
||||
// The first element must be force refreshed otherwise
|
||||
// it will use the old one realized
|
||||
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
|
||||
// Now we need to refresh the first element of each column
|
||||
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
|
||||
// Finally we need to refresh the whole layout when we reset
|
||||
if (context.ItemCount > 0)
|
||||
{
|
||||
for (int i = 0; i < context.ItemCount; i++)
|
||||
{
|
||||
RecycleElementAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
columnLayout.Clear();
|
||||
items.Clear();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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 Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
[DependencyProperty("IsWidthRestricted", typeof(bool), true)]
|
||||
[DependencyProperty("IsHeightRestricted", typeof(bool), true)]
|
||||
internal sealed partial class SizeRestrictedContentControl : ContentControl
|
||||
{
|
||||
private double minContentWidth;
|
||||
private double minContentHeight;
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
if (Content is FrameworkElement element)
|
||||
{
|
||||
element.Measure(availableSize);
|
||||
Size contentDesiredSize = element.DesiredSize;
|
||||
Size contentActualOrDesiredSize = new(
|
||||
Math.Max(element.ActualWidth, contentDesiredSize.Width),
|
||||
Math.Max(element.ActualHeight, contentDesiredSize.Height));
|
||||
|
||||
if (IsWidthRestricted)
|
||||
{
|
||||
if (contentActualOrDesiredSize.Width > minContentWidth)
|
||||
{
|
||||
minContentWidth = contentActualOrDesiredSize.Width;
|
||||
}
|
||||
|
||||
element.MinWidth = minContentWidth;
|
||||
}
|
||||
|
||||
if (IsHeightRestricted)
|
||||
{
|
||||
if (contentActualOrDesiredSize.Height > minContentHeight)
|
||||
{
|
||||
minContentHeight = contentActualOrDesiredSize.Height;
|
||||
}
|
||||
|
||||
element.MinHeight = minContentHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return base.MeasureOverride(availableSize);
|
||||
}
|
||||
}
|
||||
11
src/Snap.Hutao/Snap.Hutao/Control/Theme/ComboBox.xaml
Normal file
11
src/Snap.Hutao/Snap.Hutao/Control/Theme/ComboBox.xaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style
|
||||
x:Key="CommandBarComboBoxStyle"
|
||||
BasedOn="{StaticResource DefaultComboBoxStyle}"
|
||||
TargetType="ComboBox">
|
||||
<Setter Property="Padding" Value="12,7,0,7"/>
|
||||
<Setter Property="Height" Value="36"/>
|
||||
</Style>
|
||||
<!-- https://github.com/microsoft/microsoft-ui-xaml/issues/4811 -->
|
||||
<x:Int32 x:Key="__DiscardPageOverride">0</x:Int32>
|
||||
</ResourceDictionary>
|
||||
@@ -93,4 +93,19 @@
|
||||
<Style BasedOn="{StaticResource DefaultScrollViewerStyle}" TargetType="ScrollViewer">
|
||||
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
|
||||
</Style>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlTextForeground" Color="{ThemeResource TextFillColorPrimary}"/>
|
||||
<Thickness x:Key="TextControlTextMargin">1,0</Thickness>
|
||||
<Style
|
||||
x:Key="TextControlTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextControlTextForeground}"/>
|
||||
<Setter Property="Margin" Value="{StaticResource TextControlTextMargin}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="HorizontalTextAlignment" Value="Center"/>
|
||||
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
|
||||
<Setter Property="TextWrapping" Value="NoWrap"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -17,6 +17,9 @@
|
||||
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing2Template">
|
||||
<StackPanel Orientation="Horizontal" Spacing="2"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="StackPanelSpacing4Template">
|
||||
<StackPanel Spacing="4"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="UniformGridColumns2Spacing2Template">
|
||||
<cwcont:UniformGrid
|
||||
ColumnSpacing="2"
|
||||
|
||||
9
src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IPinnable.cs
Normal file
9
src/Snap.Hutao/Snap.Hutao/Core/Abstraction/IPinnable.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Abstraction;
|
||||
|
||||
internal interface IPinnable<TData>
|
||||
{
|
||||
ref readonly TData GetPinnableReference();
|
||||
}
|
||||
@@ -63,8 +63,10 @@ internal static partial class IocHttpClientConfiguration
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", SaltConstants.CNVersion);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-client_type", "2");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_name", string.Empty);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-game_biz", "bbs_cn");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-sdk_version", "2.16.0");
|
||||
//client.DefaultRequestHeaders.Add("x-rpc-tool_verison", "v4.2.2-ys");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Discord;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
@@ -165,6 +166,8 @@ internal sealed partial class Activation : IActivation
|
||||
|
||||
serviceProvider.GetRequiredService<MainWindow>();
|
||||
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
serviceProvider
|
||||
.GetRequiredService<IMetadataService>()
|
||||
.As<IMetadataServiceInitialization>()?
|
||||
@@ -176,6 +179,11 @@ internal sealed partial class Activation : IActivation
|
||||
.As<IHutaoUserServiceInitialization>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget();
|
||||
|
||||
serviceProvider
|
||||
.GetRequiredService<IDiscordService>()
|
||||
.SetNormalActivity()
|
||||
.SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ internal sealed class RuntimeOptions : IOptions<RuntimeOptions>
|
||||
{
|
||||
this.logger = logger;
|
||||
|
||||
AppLaunchTime = DateTimeOffset.UtcNow;
|
||||
|
||||
DataFolder = GetDataFolderPath();
|
||||
LocalCache = ApplicationData.Current.LocalCacheFolder.Path;
|
||||
InstalledLocation = Package.Current.InstalledLocation.Path;
|
||||
@@ -96,6 +98,8 @@ internal sealed class RuntimeOptions : IOptions<RuntimeOptions>
|
||||
/// </summary>
|
||||
public bool IsElevated { get => isElevated ??= GetElevated(); }
|
||||
|
||||
public DateTimeOffset AppLaunchTime { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RuntimeOptions Value { get => this; }
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Extension;
|
||||
/// <see cref="StringBuilder"/> 扩展方法
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class StringBuilderExtensions
|
||||
internal static class StringBuilderExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 当条件符合时执行 <see cref="StringBuilder.Append(string?)"/>
|
||||
@@ -37,4 +37,21 @@ internal static class StringBuilderExtensions
|
||||
{
|
||||
return condition ? sb.Append(value) : sb;
|
||||
}
|
||||
|
||||
public static string ToStringTrimEndReturn(this StringBuilder builder)
|
||||
{
|
||||
Must.Argument(builder.Length >= 1, "StringBuilder 的长度必须大于 0");
|
||||
int remove = 0;
|
||||
if (builder[^1] is '\n')
|
||||
{
|
||||
remove = 1;
|
||||
|
||||
if (builder.Length >= 2 && builder[^2] is '\r')
|
||||
{
|
||||
remove = 2;
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString(0, builder.Length - remove);
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,19 @@ internal static class WinRTExtension
|
||||
{
|
||||
public static bool IsDisposed(this IWinRTObject obj)
|
||||
{
|
||||
return GetDisposed(obj.NativeObject);
|
||||
IObjectReference objectReference = obj.NativeObject;
|
||||
|
||||
lock (GetDisposedLock(objectReference))
|
||||
{
|
||||
return GetDisposed(objectReference);
|
||||
}
|
||||
}
|
||||
|
||||
// protected bool disposed;
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name ="disposed")]
|
||||
private static extern ref bool GetDisposed(IObjectReference objRef);
|
||||
|
||||
// private object _disposedLock
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposedLock")]
|
||||
private static extern ref object GetDisposedLock(IObjectReference objRef);
|
||||
}
|
||||
@@ -16,8 +16,8 @@ namespace Snap.Hutao;
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
|
||||
{
|
||||
private const int MinWidth = 1200;
|
||||
private const int MinHeight = 750;
|
||||
private const int MinWidth = 1000;
|
||||
private const int MinHeight = 600;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
private readonly ILogger<MainWindow> logger;
|
||||
|
||||
555
src/Snap.Hutao/Snap.Hutao/Migrations/20231126113631_AddUserLastUpdateTime.Designer.cs
generated
Normal file
555
src/Snap.Hutao/Snap.Hutao/Migrations/20231126113631_AddUserLastUpdateTime.Designer.cs
generated
Normal file
@@ -0,0 +1,555 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20231126113631_AddUserLastUpdateTime")]
|
||||
partial class AddUserLastUpdateTime
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("CalculatorRefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("GameRecordRefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("ShowcaseRefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("cultivate_entries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Count")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("EntryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsFinished")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("EntryId");
|
||||
|
||||
b.ToTable("cultivate_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachedUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("cultivate_projects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("RefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Count")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AppendPropIdList")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MainPropId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_reliquaries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PromoteLevel")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_weapons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpireTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("object_cache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("ScheduleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SpiralAbyss")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("spiral_abysses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CookieToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("CookieTokenLastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Fingerprint")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsOversea")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LToken")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("Ltoken");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SToken")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("Stoken");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
|
||||
.WithMany()
|
||||
.HasForeignKey("EntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddUserLastUpdateTime : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "CookieTokenLastUpdateTime",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
||||
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "FingerprintLastUpdateTime",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CookieTokenLastUpdateTime",
|
||||
table: "users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FingerprintLastUpdateTime",
|
||||
table: "users");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.13");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
@@ -428,9 +428,15 @@ namespace Snap.Hutao.Migrations
|
||||
b.Property<string>("CookieToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("CookieTokenLastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Fingerprint")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsOversea")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
||||
@@ -106,6 +106,8 @@ internal sealed partial class SettingEntry
|
||||
|
||||
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
|
||||
|
||||
public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 多倍启动
|
||||
/// </summary>
|
||||
|
||||
@@ -65,6 +65,10 @@ internal sealed class User : ISelectable, IMappingFrom<User, Cookie, bool>
|
||||
/// </summary>
|
||||
public string? Fingerprint { get; set; }
|
||||
|
||||
public DateTimeOffset FingerprintLastUpdateTime { get; set; }
|
||||
|
||||
public DateTimeOffset CookieTokenLastUpdateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的用户
|
||||
/// </summary>
|
||||
|
||||
@@ -14,32 +14,32 @@ internal static class IntrinsicFrozen
|
||||
/// <summary>
|
||||
/// 所属地区
|
||||
/// </summary>
|
||||
public static readonly FrozenSet<string> AssociationTypes = Enum.GetValues<AssociationType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
public static FrozenSet<string> AssociationTypes { get; } = Enum.GetValues<AssociationType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 武器类型
|
||||
/// </summary>
|
||||
public static readonly FrozenSet<string> WeaponTypes = Enum.GetValues<WeaponType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
public static FrozenSet<string> WeaponTypes { get; } = Enum.GetValues<WeaponType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 物品类型
|
||||
/// </summary>
|
||||
public static readonly FrozenSet<string> ItemQualities = Enum.GetValues<QualityType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
public static FrozenSet<string> ItemQualities { get; } = Enum.GetValues<QualityType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 身材类型
|
||||
/// </summary>
|
||||
public static readonly FrozenSet<string> BodyTypes = Enum.GetValues<BodyType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
public static FrozenSet<string> BodyTypes { get; } = Enum.GetValues<BodyType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 战斗属性
|
||||
/// </summary>
|
||||
public static readonly FrozenSet<string> FightProperties = Enum.GetValues<FightProperty>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
public static FrozenSet<string> FightProperties { get; } = Enum.GetValues<FightProperty>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
|
||||
|
||||
/// <summary>
|
||||
/// 元素名称
|
||||
/// </summary>
|
||||
public static readonly FrozenSet<string> ElementNames = FrozenSet.ToFrozenSet(
|
||||
public static FrozenSet<string> ElementNames { get; } = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
SH.ModelIntrinsicElementNameFire,
|
||||
SH.ModelIntrinsicElementNameWater,
|
||||
@@ -50,7 +50,7 @@ internal static class IntrinsicFrozen
|
||||
SH.ModelIntrinsicElementNameRock,
|
||||
]);
|
||||
|
||||
public static readonly FrozenSet<string> MaterialTypeDescriptions = FrozenSet.ToFrozenSet(
|
||||
public static FrozenSet<string> MaterialTypeDescriptions { get; } = FrozenSet.ToFrozenSet(
|
||||
[
|
||||
SH.ModelMetadataMaterialCharacterAndWeaponEnhancementMaterial,
|
||||
SH.ModelMetadataMaterialCharacterEXPMaterial,
|
||||
|
||||
@@ -14,8 +14,5 @@
|
||||
<ReservedName>胡桃工具箱</ReservedName>
|
||||
</ReservedNames>
|
||||
</ProductReservedInfo>
|
||||
<AccountPackageIdentityNames>
|
||||
<MainPackageIdentityName>60568DGPStudio.SnapGenshinResin</MainPackageIdentityName>
|
||||
</AccountPackageIdentityNames>
|
||||
<PackageInfoList LandingUrl="https://developer.microsoft.com/dashboard/Application?appId=9PH4NXJ2JN52" />
|
||||
</StoreAssociation>
|
||||
68
src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest
Normal file
68
src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
IgnorableNamespaces="com uap desktop rescap mp">
|
||||
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutaoDev"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.8.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao Dev</DisplayName>
|
||||
<PublisherDisplayName>DGP Studio</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="en-us"/>
|
||||
<Resource Language="zh-cn"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Snap Hutao Dev"
|
||||
Description="A Snap Hutao Dev Software"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square71x71Logo="Assets\SmallTile.png" Square310x310Logo="Assets\LargeTile.png"/>
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<desktop:Extension Category="windows.toastNotificationActivation">
|
||||
<desktop:ToastNotificationActivation ToastActivatorCLSID="5760ec4d-f7e8-4666-a965-9886d7dffe7d"/>
|
||||
</desktop:Extension>
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer Executable="Snap.Hutao.exe" Arguments="-ToastActivated" DisplayName="Snap Hutao Dev Toast Activator">
|
||||
<com:Class Id="5760ec4d-f7e8-4666-a965-9886d7dffe7d" DisplayName="Snap Hutao Dev Toast Activator"/>
|
||||
</com:ExeServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="hutao">
|
||||
<uap:DisplayName>胡桃 Dev</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust"/>
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -948,7 +948,7 @@
|
||||
<value>Unable to find cached metadata file</value>
|
||||
</data>
|
||||
<data name="ServiceMetadataHttpRequestFailed" xml:space="preserve">
|
||||
<value>HTTP {0} | Error:{1}:元数据校验文件下载失败</value>
|
||||
<value>HTTP {0} | Error {1}: Failed to download metadata verification file</value>
|
||||
</data>
|
||||
<data name="ServiceMetadataNotInitialized" xml:space="preserve">
|
||||
<value>Metadata service has not been initialized or failed to initialize</value>
|
||||
@@ -2012,6 +2012,9 @@
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioHeader" xml:space="preserve">
|
||||
<value>Screen Resolution</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioPlaceHolder" xml:space="preserve">
|
||||
<value>Resolution Quick Set</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceBorderlessDescription" xml:space="preserve">
|
||||
<value>Create window as popup, without frame</value>
|
||||
</data>
|
||||
@@ -2057,6 +2060,12 @@
|
||||
<data name="ViewPageLaunchGameConfigurationSaveHint" xml:space="preserve">
|
||||
<value>All options will be saved only after the game is launched successfully.</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityDescription" xml:space="preserve">
|
||||
<value>Set My Discord Activity Status When I'm in the Game</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityHeader" xml:space="preserve">
|
||||
<value>Discord Activity</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameFileHeader" xml:space="preserve">
|
||||
<value>File</value>
|
||||
</data>
|
||||
@@ -2597,6 +2606,9 @@
|
||||
<data name="ViewUserDocumentationHeader" xml:space="preserve">
|
||||
<value>Document</value>
|
||||
</data>
|
||||
<data name="ViewUserNoUserHint" xml:space="preserve">
|
||||
<value>No Login</value>
|
||||
</data>
|
||||
<data name="ViewUserRefreshCookieTokenSuccess" xml:space="preserve">
|
||||
<value>Refresh CookieToken successfully</value>
|
||||
</data>
|
||||
@@ -2757,7 +2769,7 @@
|
||||
<value>Wrong UID format</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode404" xml:space="preserve">
|
||||
<value>Role UID does not exist</value>
|
||||
<value>Role UID does not exist, please try again later</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode424" xml:space="preserve">
|
||||
<value>Game in maintenance</value>
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
<value>キャンセル</value>
|
||||
</data>
|
||||
<data name="ContentDialogCompletePrimaryButtonText" xml:space="preserve">
|
||||
<value>完了</value>
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="ContentDialogConfirmPrimaryButtonText" xml:space="preserve">
|
||||
<value>確定</value>
|
||||
@@ -600,10 +600,10 @@
|
||||
<value>{0} つのアチーブメントを追加 | {1} つのアチーブメントを更新 |{2} つのアチーブメントを削除</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUIAFImportPickerFilterText" xml:space="preserve">
|
||||
<value>UIAF Json 文件</value>
|
||||
<value>UIAF Json ファイル</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUIAFImportPickerTitile" xml:space="preserve">
|
||||
<value>打开 UIAF Json 文件</value>
|
||||
<value>UIAF Json ファイルを開く</value>
|
||||
</data>
|
||||
<data name="ServiceAchievementUserdataCorruptedInnerIdNotUnique" xml:space="preserve">
|
||||
<value>複数の同一アチーブメント Idがアーカイブに混在しています</value>
|
||||
@@ -792,10 +792,10 @@
|
||||
<value>参量物質変化器は使用可能</value>
|
||||
</data>
|
||||
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
|
||||
<value>正在提瓦特大陆中探索</value>
|
||||
<value>テイワット大陸を探索中</value>
|
||||
</data>
|
||||
<data name="ServiceDiscordGameLaunchedBy" xml:space="preserve">
|
||||
<value>由 {0} 启动</value>
|
||||
<value>スタートから {0}</value>
|
||||
</data>
|
||||
<data name="ServiceGachaLogArchiveCollectionUserdataCorruptedMessage" xml:space="preserve">
|
||||
<value>{0}:祈願履歴を確認できません</value>
|
||||
@@ -882,7 +882,7 @@
|
||||
<value>ゲーム本体を選択する</value>
|
||||
</data>
|
||||
<data name="ServiceGameLocatorPickerFilterText" xml:space="preserve">
|
||||
<value>游戏本体</value>
|
||||
<value>ゲームクライアント</value>
|
||||
</data>
|
||||
<data name="ServiceGameLocatorUnityLogFileNotFound" xml:space="preserve">
|
||||
<value>Unity ログファイルが見つかりません</value>
|
||||
@@ -948,7 +948,7 @@
|
||||
<value>キャッシュされたメタデータファイルが見つかりませんでした</value>
|
||||
</data>
|
||||
<data name="ServiceMetadataHttpRequestFailed" xml:space="preserve">
|
||||
<value>HTTP {0} | Error:{1}:元数据校验文件下载失败</value>
|
||||
<value>HTTP {0} | Error {1}: メタデータ検証ファイルのダウンロードに失敗しました。</value>
|
||||
</data>
|
||||
<data name="ServiceMetadataNotInitialized" xml:space="preserve">
|
||||
<value>メタデータサービスが初期化されていないか、または初期化できませんでした</value>
|
||||
@@ -1350,7 +1350,7 @@
|
||||
<value>{0} を削除してもよろしいでしょうか?</value>
|
||||
</data>
|
||||
<data name="ViewModelAchievementUIAFExportPickerTitle" xml:space="preserve">
|
||||
<value>导出 UIAF Json 文件到指定路径</value>
|
||||
<value>UIAF Json ファイルを指定した場所へエクスポート</value>
|
||||
</data>
|
||||
<data name="ViewModelAvatarPropertyBatchCultivateProgressTitle" xml:space="preserve">
|
||||
<value>素材リスト取得中、しばらくお待ちください</value>
|
||||
@@ -1479,13 +1479,13 @@
|
||||
<value>胡桃クラウドで祈願履歴を同期します</value>
|
||||
</data>
|
||||
<data name="ViewModelGachaLogUIGFExportPickerTitle" xml:space="preserve">
|
||||
<value>导出 UIGF Json 文件到指定路径</value>
|
||||
<value>UIGF Json ファイルを指定した場所へエクスポート</value>
|
||||
</data>
|
||||
<data name="ViewModelGachaLogUploadToHutaoCloudProgress" xml:space="preserve">
|
||||
<value>胡桃クラウドにアップロード中</value>
|
||||
</data>
|
||||
<data name="ViewModelGachaUIGFImportPickerTitile" xml:space="preserve">
|
||||
<value>导入 UIGF Json 文件</value>
|
||||
<value>UIGF Json ファイルをインポート</value>
|
||||
</data>
|
||||
<data name="ViewModelGuideActionAgreement" xml:space="preserve">
|
||||
<value>上記の規約を熟読し、それに同意します</value>
|
||||
@@ -2012,6 +2012,9 @@
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioHeader" xml:space="preserve">
|
||||
<value>解像度</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioPlaceHolder" xml:space="preserve">
|
||||
<value>快捷设置分辨率</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceBorderlessDescription" xml:space="preserve">
|
||||
<value>枠の無いポップアップウィンドウとして作成します。</value>
|
||||
</data>
|
||||
@@ -2057,6 +2060,12 @@
|
||||
<data name="ViewPageLaunchGameConfigurationSaveHint" xml:space="preserve">
|
||||
<value>これらの設定はゲームが正常に起動した時のみ保存されます。</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityDescription" xml:space="preserve">
|
||||
<value>ゲームをプレイしている時に、Discord Activityのステータスを変更します。</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityHeader" xml:space="preserve">
|
||||
<value>Discord Activity</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameFileHeader" xml:space="preserve">
|
||||
<value>ファイル</value>
|
||||
</data>
|
||||
@@ -2597,6 +2606,9 @@
|
||||
<data name="ViewUserDocumentationHeader" xml:space="preserve">
|
||||
<value>ドキュメント</value>
|
||||
</data>
|
||||
<data name="ViewUserNoUserHint" xml:space="preserve">
|
||||
<value>尚未登录</value>
|
||||
</data>
|
||||
<data name="ViewUserRefreshCookieTokenSuccess" xml:space="preserve">
|
||||
<value>CookieTokenを更新しました。</value>
|
||||
</data>
|
||||
@@ -2757,7 +2769,7 @@
|
||||
<value>UIDは正しくありません</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode404" xml:space="preserve">
|
||||
<value>UID は存在しません</value>
|
||||
<value>ロール UIDが存在しません。時間を置いてもう一度試してください。</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode424" xml:space="preserve">
|
||||
<value>ゲームメンテナンス中</value>
|
||||
|
||||
@@ -2012,6 +2012,9 @@
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioHeader" xml:space="preserve">
|
||||
<value>分辨率</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioPlaceHolder" xml:space="preserve">
|
||||
<value>快捷设置分辨率</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceBorderlessDescription" xml:space="preserve">
|
||||
<value>테두리 없는 창모드</value>
|
||||
</data>
|
||||
@@ -2057,6 +2060,12 @@
|
||||
<data name="ViewPageLaunchGameConfigurationSaveHint" xml:space="preserve">
|
||||
<value>모든 설정은 게임을 성공적으로 실행한 후에 저장됩니다</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityDescription" xml:space="preserve">
|
||||
<value>在我游戏时设置 Discord Activity 状态</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityHeader" xml:space="preserve">
|
||||
<value>Discord Activity</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameFileHeader" xml:space="preserve">
|
||||
<value>文件</value>
|
||||
</data>
|
||||
@@ -2597,6 +2606,9 @@
|
||||
<data name="ViewUserDocumentationHeader" xml:space="preserve">
|
||||
<value>文档</value>
|
||||
</data>
|
||||
<data name="ViewUserNoUserHint" xml:space="preserve">
|
||||
<value>尚未登录</value>
|
||||
</data>
|
||||
<data name="ViewUserRefreshCookieTokenSuccess" xml:space="preserve">
|
||||
<value>CookieToken을 동기화 했습니다</value>
|
||||
</data>
|
||||
@@ -2757,7 +2769,7 @@
|
||||
<value>错误的 UID 格式</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode404" xml:space="preserve">
|
||||
<value>角色 UID 不存在</value>
|
||||
<value>角色 UID 不存在,请稍候再试</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode424" xml:space="preserve">
|
||||
<value>游戏维护中</value>
|
||||
|
||||
@@ -506,6 +506,9 @@
|
||||
<data name="MustSelectUserAndUid" xml:space="preserve">
|
||||
<value>必须先选择一个用户与角色</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceDeleteEntrySucceed" xml:space="preserve">
|
||||
<value>删除了 Uid:{0} 的 {1} 条祈愿记录</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceInsufficientRecordSlot" xml:space="preserve">
|
||||
<value>胡桃云保存的祈愿记录存档数已达当前账号上限</value>
|
||||
</data>
|
||||
@@ -518,6 +521,12 @@
|
||||
<data name="ServerGachaLogServiceServerDatabaseError" xml:space="preserve">
|
||||
<value>数据异常,无法保存至云端,请勿跨账号上传或尝试删除云端数据后重试</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceUploadEntrySucceed" xml:space="preserve">
|
||||
<value>上传了 Uid:{0} 的 {1} 条祈愿记录,存储了 {2} 条</value>
|
||||
</data>
|
||||
<data name="ServerPassportLoginRequired" xml:space="preserve">
|
||||
<value>请先登录或注册胡桃账号</value>
|
||||
</data>
|
||||
<data name="ServerPassportLoginSucceed" xml:space="preserve">
|
||||
<value>登录成功</value>
|
||||
</data>
|
||||
@@ -2012,6 +2021,9 @@
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioHeader" xml:space="preserve">
|
||||
<value>分辨率</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioPlaceHolder" xml:space="preserve">
|
||||
<value>快捷设置分辨率</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceBorderlessDescription" xml:space="preserve">
|
||||
<value>将窗口创建为弹出窗口,不带框架</value>
|
||||
</data>
|
||||
@@ -2057,6 +2069,12 @@
|
||||
<data name="ViewPageLaunchGameConfigurationSaveHint" xml:space="preserve">
|
||||
<value>所有选项仅会在启动游戏成功后保存</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityDescription" xml:space="preserve">
|
||||
<value>在我游戏时设置 Discord Activity 状态</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityHeader" xml:space="preserve">
|
||||
<value>Discord Activity</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameFileHeader" xml:space="preserve">
|
||||
<value>文件</value>
|
||||
</data>
|
||||
@@ -2597,6 +2615,9 @@
|
||||
<data name="ViewUserDocumentationHeader" xml:space="preserve">
|
||||
<value>文档</value>
|
||||
</data>
|
||||
<data name="ViewUserNoUserHint" xml:space="preserve">
|
||||
<value>尚未登录</value>
|
||||
</data>
|
||||
<data name="ViewUserRefreshCookieTokenSuccess" xml:space="preserve">
|
||||
<value>刷新 CookieToken 成功</value>
|
||||
</data>
|
||||
@@ -2757,7 +2778,7 @@
|
||||
<value>错误的 UID 格式</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode404" xml:space="preserve">
|
||||
<value>角色 UID 不存在</value>
|
||||
<value>角色 UID 不存在,请稍候再试</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode424" xml:space="preserve">
|
||||
<value>游戏维护中</value>
|
||||
|
||||
@@ -522,10 +522,10 @@
|
||||
<value>登录成功</value>
|
||||
</data>
|
||||
<data name="ServerPassportRegisterSucceed" xml:space="preserve">
|
||||
<value>注册成功</value>
|
||||
<value>註冊成功</value>
|
||||
</data>
|
||||
<data name="ServerPassportResetPasswordSucceed" xml:space="preserve">
|
||||
<value>新密码设置成功</value>
|
||||
<value>新密碼設定成功</value>
|
||||
</data>
|
||||
<data name="ServerPassportServiceEmailHasNotRegistered" xml:space="preserve">
|
||||
<value>当前邮箱尚未注册</value>
|
||||
@@ -2012,6 +2012,9 @@
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioHeader" xml:space="preserve">
|
||||
<value>分辨率</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceAspectRatioPlaceHolder" xml:space="preserve">
|
||||
<value>快捷设置分辨率</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceBorderlessDescription" xml:space="preserve">
|
||||
<value>將窗口創建為彈出窗口,不帶邊框</value>
|
||||
</data>
|
||||
@@ -2057,6 +2060,12 @@
|
||||
<data name="ViewPageLaunchGameConfigurationSaveHint" xml:space="preserve">
|
||||
<value>所有選項盡會在啓動游戲成功後保存</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityDescription" xml:space="preserve">
|
||||
<value>在我游戏时设置 Discord Activity 状态</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityHeader" xml:space="preserve">
|
||||
<value>Discord Activity</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameFileHeader" xml:space="preserve">
|
||||
<value>文件</value>
|
||||
</data>
|
||||
@@ -2597,6 +2606,9 @@
|
||||
<data name="ViewUserDocumentationHeader" xml:space="preserve">
|
||||
<value>文檔</value>
|
||||
</data>
|
||||
<data name="ViewUserNoUserHint" xml:space="preserve">
|
||||
<value>尚未登录</value>
|
||||
</data>
|
||||
<data name="ViewUserRefreshCookieTokenSuccess" xml:space="preserve">
|
||||
<value>刷新 CookieToken 成功</value>
|
||||
</data>
|
||||
@@ -2757,7 +2769,7 @@
|
||||
<value>錯誤的 UID 格式</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode404" xml:space="preserve">
|
||||
<value>角色 UID 不存在</value>
|
||||
<value>角色UID不存在,請稍候再試</value>
|
||||
</data>
|
||||
<data name="WebEnkaResponseStatusCode424" xml:space="preserve">
|
||||
<value>遊戲維護中</value>
|
||||
|
||||
@@ -29,9 +29,9 @@ internal static class AppOptionsExtension
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetGameFileName(this AppOptions appOptions, [NotNullWhen(true)] out string? gameFileName)
|
||||
public static bool TryGetGamePathAndGameFileName(this AppOptions appOptions, out string gamePath, [NotNullWhen(true)] out string? gameFileName)
|
||||
{
|
||||
string gamePath = appOptions.GamePath;
|
||||
gamePath = appOptions.GamePath;
|
||||
|
||||
gameFileName = Path.GetFileName(gamePath);
|
||||
if (string.IsNullOrEmpty(gameFileName))
|
||||
|
||||
@@ -127,9 +127,6 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
{
|
||||
WebDailyNote dailyNote = dailyNoteResponse.Data;
|
||||
|
||||
// 发送通知
|
||||
await dailyNoteNotificationOperation.SendAsync(entry).ConfigureAwait(false);
|
||||
|
||||
// 集合内的实时便笺与数据库取出的非同一个对象,需要分别更新
|
||||
// cache
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
@@ -144,9 +141,14 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
}
|
||||
|
||||
// database
|
||||
entry.UpdateDailyNote(dailyNote);
|
||||
await dailyNoteDbService.UpdateDailyNoteEntryAsync(entry).ConfigureAwait(false);
|
||||
await dailyNoteWebhookOperation.TryPostDailyNoteToWebhookAsync(dailyNote).ConfigureAwait(false);
|
||||
{
|
||||
entry.UpdateDailyNote(dailyNote);
|
||||
|
||||
// 发送通知必须早于数据库更新,否则会导致通知重复
|
||||
await dailyNoteNotificationOperation.SendAsync(entry).ConfigureAwait(false);
|
||||
await dailyNoteDbService.UpdateDailyNoteEntryAsync(entry).ConfigureAwait(false);
|
||||
await dailyNoteWebhookOperation.TryPostDailyNoteToWebhookAsync(dailyNote).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,28 @@ namespace Snap.Hutao.Service.Discord;
|
||||
|
||||
internal static class DiscordController
|
||||
{
|
||||
// https://discord.com/developers/applications
|
||||
private const long HutaoAppId = 1173950861647552623L;
|
||||
private const long YuanshenId = 1175743396028088370L;
|
||||
private const long GenshinImpactId = 1175747474384760962L;
|
||||
|
||||
private static readonly WaitCallback RunDiscordRunCallbacks = DiscordRunCallbacks;
|
||||
private static readonly CancellationTokenSource StopTokenSource = new();
|
||||
private static readonly object SyncRoot = new();
|
||||
|
||||
private static Snap.Discord.GameSDK.Discord? discordManager;
|
||||
private static bool isInitialized;
|
||||
|
||||
public static async ValueTask<Result> ClearActivityAsync()
|
||||
public static async ValueTask<Result> SetDefaultActivityAsync(DateTimeOffset startTime)
|
||||
{
|
||||
ResetManagerOrIgnore(HutaoAppId);
|
||||
ActivityManager activityManager = discordManager.GetActivityManager();
|
||||
return await activityManager.ClearActivityAsync().ConfigureAwait(false);
|
||||
|
||||
Activity activity = default;
|
||||
activity.Timestamps.Start = startTime.ToUnixTimeSeconds();
|
||||
activity.Assets.LargeImage = "icon";
|
||||
activity.Assets.LargeText = SH.AppName;
|
||||
|
||||
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async ValueTask<Result> SetPlayingYuanShenAsync()
|
||||
@@ -72,7 +78,13 @@ internal static class DiscordController
|
||||
lock (SyncRoot)
|
||||
{
|
||||
StopTokenSource.Cancel();
|
||||
discordManager?.Dispose();
|
||||
try
|
||||
{
|
||||
discordManager?.Dispose();
|
||||
}
|
||||
catch (SEHException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,17 +99,16 @@ internal static class DiscordController
|
||||
lock (SyncRoot)
|
||||
{
|
||||
discordManager?.Dispose();
|
||||
discordManager = new(clientId, CreateFlags.NoRequireDiscord);
|
||||
discordManager.SetLogHook(Snap.Discord.GameSDK.LogLevel.Debug, SetLogHookHandler.Create(&DebugWriteDiscordMessage));
|
||||
}
|
||||
|
||||
discordManager = new(clientId, CreateFlags.NoRequireDiscord);
|
||||
discordManager.SetLogHook(Snap.Discord.GameSDK.LogLevel.Debug, SetLogHookHandler.Create(&DebugWriteDiscordMessage));
|
||||
|
||||
if (isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadPool.UnsafeQueueUserWorkItem(RunDiscordRunCallbacks, StopTokenSource.Token);
|
||||
DiscordRunCallbacksAsync(StopTokenSource.Token).SafeForget();
|
||||
isInitialized = true;
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
|
||||
@@ -109,18 +120,46 @@ internal static class DiscordController
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
private static void DiscordRunCallbacks(object? state)
|
||||
private static async ValueTask DiscordRunCallbacksAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
CancellationToken cancellationToken = (CancellationToken)state!;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
using (PeriodicTimer timer = new(TimeSpan.FromMilliseconds(1000)))
|
||||
{
|
||||
lock (SyncRoot)
|
||||
try
|
||||
{
|
||||
discordManager?.RunCallbacks();
|
||||
}
|
||||
while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Thread.Sleep(100);
|
||||
lock (SyncRoot)
|
||||
{
|
||||
try
|
||||
{
|
||||
discordManager?.RunCallbacks();
|
||||
}
|
||||
catch (ResultException ex)
|
||||
{
|
||||
// If result is Ok
|
||||
// Maybe the connection is reset.
|
||||
if (ex.Result is not Result.Ok)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK ERROR]:{ex.Result:D} {ex.Result}");
|
||||
}
|
||||
}
|
||||
catch (SEHException ex)
|
||||
{
|
||||
// Known error codes:
|
||||
// 0x80004005 E_FAIL
|
||||
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK ERROR]:0x{ex.ErrorCode:X}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,28 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
|
||||
namespace Snap.Hutao.Service.Discord;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IDiscordService))]
|
||||
internal sealed partial class DiscordService : IDiscordService, IDisposable
|
||||
{
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
|
||||
public async ValueTask SetPlayingActivity(bool isOversea)
|
||||
{
|
||||
_ = isOversea
|
||||
? await DiscordController.SetPlayingGenshinImpactAsync().ConfigureAwait(false)
|
||||
: await DiscordController.SetPlayingYuanShenAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async ValueTask SetNormalActivity()
|
||||
{
|
||||
_ = await DiscordController.SetDefaultActivityAsync(runtimeOptions.AppLaunchTime).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DiscordController.Stop();
|
||||
|
||||
@@ -5,4 +5,7 @@ namespace Snap.Hutao.Service.Discord;
|
||||
|
||||
internal interface IDiscordService
|
||||
{
|
||||
ValueTask SetNormalActivity();
|
||||
|
||||
ValueTask SetPlayingActivity(bool isOversea);
|
||||
}
|
||||
@@ -38,6 +38,7 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
private bool? isMonitorEnabled;
|
||||
private AspectRatio? selectedAspectRatio;
|
||||
private bool? useStarwardPlayTimeStatistics;
|
||||
private bool? setDiscordActivityWhenPlaying;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的启动游戏选项
|
||||
@@ -190,6 +191,12 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
set => SetOption(ref useStarwardPlayTimeStatistics, SettingEntry.LaunchUseStarwardPlayTimeStatistics, value);
|
||||
}
|
||||
|
||||
public bool SetDiscordActivityWhenPlaying
|
||||
{
|
||||
get => GetOption(ref setDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, true);
|
||||
set => SetOption(ref setDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, value);
|
||||
}
|
||||
|
||||
private static void InitializeMonitors(List<NameValue<int>> monitors)
|
||||
{
|
||||
// This list can't use foreach
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Service.Discord;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using Snap.Hutao.Service.Game.Unlocker;
|
||||
using System.IO;
|
||||
@@ -17,17 +19,18 @@ namespace Snap.Hutao.Service.Game.Process;
|
||||
internal sealed partial class GameProcessService : IGameProcessService
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IDiscordService discordService;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly AppOptions appOptions;
|
||||
|
||||
private volatile int runningGamesCounter;
|
||||
private volatile bool isGameRunning;
|
||||
|
||||
public bool IsGameRunning()
|
||||
{
|
||||
if (runningGamesCounter == 0)
|
||||
if (isGameRunning)
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return System.Diagnostics.Process.GetProcessesByName(YuanShenProcessName).Length > 0
|
||||
@@ -41,21 +44,24 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
return;
|
||||
}
|
||||
|
||||
string gamePath = appOptions.GamePath;
|
||||
ArgumentException.ThrowIfNullOrEmpty(gamePath);
|
||||
if (!appOptions.TryGetGamePathAndGameFileName(out string gamePath, out string? gameFileName))
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(gamePath);
|
||||
return; // null check passing, actually never reach.
|
||||
}
|
||||
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileName);
|
||||
|
||||
progress.Report(new(LaunchPhase.ProcessInitializing, SH.ServiceGameLaunchPhaseProcessInitializing));
|
||||
using (System.Diagnostics.Process game = InitializeGameProcess(gamePath))
|
||||
{
|
||||
try
|
||||
using (new GameRunningTracker(this, isOversea))
|
||||
{
|
||||
Interlocked.Increment(ref runningGamesCounter);
|
||||
game.Start();
|
||||
progress.Report(new(LaunchPhase.ProcessStarted, SH.ServiceGameLaunchPhaseProcessStarted));
|
||||
|
||||
if (launchOptions.UseStarwardPlayTimeStatistics && appOptions.TryGetGameFileName(out string? gameFileName))
|
||||
if (launchOptions.UseStarwardPlayTimeStatistics)
|
||||
{
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileName);
|
||||
await Starward.LaunchForPlayTimeStatisticsAsync(isOversea).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -84,10 +90,6 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
progress.Report(new(LaunchPhase.ProcessExited, SH.ServiceGameLaunchPhaseProcessExited));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Decrement(ref runningGamesCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,4 +133,31 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
Progress<UnlockerStatus> lockerProgress = new(unlockStatus => progress.Report(LaunchStatus.FromUnlockStatus(unlockStatus)));
|
||||
return unlocker.UnlockAsync(options, lockerProgress, token);
|
||||
}
|
||||
|
||||
private class GameRunningTracker : IDisposable
|
||||
{
|
||||
private readonly GameProcessService service;
|
||||
|
||||
public GameRunningTracker(GameProcessService service, bool isOversea)
|
||||
{
|
||||
service.isGameRunning = true;
|
||||
|
||||
if (service.launchOptions.SetDiscordActivityWhenPlaying)
|
||||
{
|
||||
service.discordService.SetPlayingActivity(isOversea);
|
||||
}
|
||||
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (service.launchOptions.SetDiscordActivityWhenPlaying)
|
||||
{
|
||||
service.discordService.SetNormalActivity();
|
||||
}
|
||||
|
||||
service.isGameRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Process;
|
||||
|
||||
internal sealed class GameProcessTracker : IDisposable
|
||||
{
|
||||
private readonly Stack<IDisposable> disposables = [];
|
||||
|
||||
public TDisposable Track<TDisposable>(TDisposable disposable)
|
||||
where TDisposable : IDisposable
|
||||
{
|
||||
disposables.Push(disposable);
|
||||
return disposable;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
while (disposables.TryPop(out IDisposable? disposable))
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,12 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Fingerprint))
|
||||
if (user.Entity.FingerprintLastUpdateTime >= DateTimeOffset.UtcNow - TimeSpan.FromDays(3))
|
||||
{
|
||||
return;
|
||||
if (!string.IsNullOrEmpty(user.Fingerprint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string model = Core.Random.GetUpperAndNumberString(6);
|
||||
@@ -77,6 +80,8 @@ internal sealed partial class UserFingerprintService : IUserFingerprintService
|
||||
|
||||
Response<DeviceFpWrapper> response = await deviceFpClient.GetFingerprintAsync(data, token).ConfigureAwait(false);
|
||||
user.Fingerprint = response.IsOk() ? response.Data.DeviceFp : string.Empty;
|
||||
|
||||
user.Entity.FingerprintLastUpdateTime = DateTimeOffset.UtcNow;
|
||||
user.NeedDbUpdateAfterResume = true;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
|
||||
|
||||
await userFingerprintService.TryInitializeAsync(user, token).ConfigureAwait(false);
|
||||
|
||||
// should not raise propery changed event here
|
||||
// Should not raise propery changed event here
|
||||
user.SetSelectedUserGameRole(user.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen), false);
|
||||
return user.IsInitialized = true;
|
||||
}
|
||||
@@ -122,9 +122,12 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
|
||||
|
||||
private async ValueTask<bool> TrySetUserCookieTokenAsync(ViewModel.User.User user, CancellationToken token)
|
||||
{
|
||||
if (user.CookieToken is not null)
|
||||
if (user.Entity.CookieTokenLastUpdateTime > DateTimeOffset.UtcNow - TimeSpan.FromDays(1))
|
||||
{
|
||||
return true;
|
||||
if (user.CookieToken is not null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Response<UidCookieToken> cookieTokenResponse = await serviceProvider
|
||||
@@ -140,6 +143,9 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
|
||||
[Cookie.ACCOUNT_ID] = user.Entity.Aid ?? string.Empty,
|
||||
[Cookie.COOKIE_TOKEN] = cookieTokenResponse.Data.CookieToken,
|
||||
};
|
||||
|
||||
user.Entity.CookieTokenLastUpdateTime = DateTimeOffset.UtcNow;
|
||||
user.NeedDbUpdateAfterResume = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
<AppxBundlePlatforms>x64</AppxBundlePlatforms>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest" Condition="'$(ConfigurationName)'!='Debug'" />
|
||||
<AppxManifest Include="Package.development.appxmanifest" Condition="'$(ConfigurationName)'=='Debug'" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Included Files -->
|
||||
<ItemGroup>
|
||||
@@ -78,6 +83,7 @@
|
||||
<None Remove="Control\Panel\PanelSelector.xaml" />
|
||||
<None Remove="Control\Theme\Card.xaml" />
|
||||
<None Remove="Control\Theme\Color.xaml" />
|
||||
<None Remove="Control\Theme\ComboBox.xaml" />
|
||||
<None Remove="Control\Theme\Converter.xaml" />
|
||||
<None Remove="Control\Theme\CornerRadius.xaml" />
|
||||
<None Remove="Control\Theme\FlyoutStyle.xaml" />
|
||||
@@ -129,6 +135,7 @@
|
||||
<None Remove="View\Card\GachaStatisticsCard.xaml" />
|
||||
<None Remove="View\Card\LaunchGameCard.xaml" />
|
||||
<None Remove="View\Card\Primitive\CardProgressBar.xaml" />
|
||||
<None Remove="View\Card\Primitive\HorizontalCard.xaml" />
|
||||
<None Remove="View\Control\BaseValueSlider.xaml" />
|
||||
<None Remove="View\Control\BottomTextControl.xaml" />
|
||||
<None Remove="View\Control\DescParamComboBox.xaml" />
|
||||
@@ -187,6 +194,11 @@
|
||||
<AdditionalFiles Include="CodeMetricsConfig.txt" />
|
||||
<AdditionalFiles Include="IdentityStructs.json" />
|
||||
<AdditionalFiles Include="stylecop.json" />
|
||||
<AdditionalFiles Include="Resource\Localization\SH.resx" />
|
||||
<AdditionalFiles Include="Resource\Localization\SH.en.resx" />
|
||||
<AdditionalFiles Include="Resource\Localization\SH.ja.resx" />
|
||||
<AdditionalFiles Include="Resource\Localization\SH.ko.resx" />
|
||||
<AdditionalFiles Include="Resource\Localization\SH.zh-Hant.resx" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Assets Files -->
|
||||
@@ -278,11 +290,11 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" />
|
||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.2.0" />
|
||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.4.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.507">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@@ -306,15 +318,16 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
<None Include="Extension\EnumerableExtension.NameValueCollection.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Resource\Localization\SH.resx" />
|
||||
<AdditionalFiles Include="Resource\Localization\SH.en.resx" />
|
||||
<AdditionalFiles Include="Resource\Localization\SH.ja.resx" />
|
||||
<AdditionalFiles Include="Resource\Localization\SH.ko.resx" />
|
||||
<AdditionalFiles Include="Resource\Localization\SH.zh-Hant.resx" />
|
||||
<Page Update="View\Card\Primitive\HorizontalCard.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Control\Theme\ComboBox.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shvg="using:Snap.Hutao.ViewModel.Game"
|
||||
@@ -60,14 +61,16 @@
|
||||
Content="{StaticResource FontIconContentSetting}"
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
ToolTipService.ToolTip="{shcm:ResourceString Name=ViewPageHomeLaunchGameSettingAction}"/>
|
||||
<ComboBox
|
||||
<shc:SizeRestrictedContentControl
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
VerticalAlignment="Bottom"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding GameAccounts}"
|
||||
PlaceholderText="{shcm:ResourceString Name=ViewCardLaunchGameSelectAccountPlaceholder}"
|
||||
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
|
||||
VerticalAlignment="Bottom">
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding GameAccounts}"
|
||||
PlaceholderText="{shcm:ResourceString Name=ViewCardLaunchGameSelectAccountPlaceholder}"
|
||||
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Button>
|
||||
</Button>
|
||||
@@ -0,0 +1,24 @@
|
||||
<UserControl
|
||||
x:Class="Snap.Hutao.View.Card.Primitive.HorizontalCard"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Border Style="{StaticResource BorderCardStyle}">
|
||||
<Grid Background="{x:Bind Background, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Child="{x:Bind Left, Mode=OneWay}"/>
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Child="{x:Bind Right, Mode=OneWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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.Card.Primitive;
|
||||
|
||||
[DependencyProperty("Left", typeof(UIElement), default!)]
|
||||
[DependencyProperty("Right", typeof(UIElement), default!)]
|
||||
internal sealed partial class HorizontalCard : UserControl
|
||||
{
|
||||
public HorizontalCard()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -13,44 +13,44 @@
|
||||
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
|
||||
|
||||
<DataTemplate x:Key="BaseValueTemplate">
|
||||
<cwc:SettingsCard Margin="0,2,0,0" Header="{Binding Name}">
|
||||
<cwc:SettingsCard
|
||||
MinHeight="40"
|
||||
Padding="16,0,42,0"
|
||||
Header="{Binding Name}">
|
||||
<TextBlock Text="{Binding Value}"/>
|
||||
</cwc:SettingsCard>
|
||||
</DataTemplate>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel VerticalAlignment="Top">
|
||||
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewControlBaseValueSliderLevel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind BaseValueInfo.CurrentLevelFormatted, Mode=OneWay}"/>
|
||||
<CheckBox
|
||||
MinWidth="0"
|
||||
Margin="16,0,8,0"
|
||||
Padding="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Content="{shcm:ResourceString Name=ViewControlBaseValueSliderPromoted}"
|
||||
IsChecked="{x:Bind BaseValueInfo.Promoted, Mode=TwoWay}"
|
||||
Visibility="{x:Bind IsPromoteVisible, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
<Slider
|
||||
MinWidth="240"
|
||||
Margin="16,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="{x:Bind BaseValueInfo.MaxLevel, Mode=OneWay}"
|
||||
Minimum="1"
|
||||
StepFrequency="1"
|
||||
Value="{x:Bind BaseValueInfo.CurrentLevel, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsExpander
|
||||
Header="{shcm:ResourceString Name=ViewControlBaseValueSliderLevel}"
|
||||
IsExpanded="True"
|
||||
ItemTemplate="{StaticResource BaseValueTemplate}"
|
||||
ItemsSource="{x:Bind BaseValueInfo.Values, Mode=OneWay}">
|
||||
<StackPanel
|
||||
Height="16"
|
||||
Margin="8,0,0,0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="16">
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind BaseValueInfo.CurrentLevelFormatted, Mode=OneWay}"/>
|
||||
<CheckBox
|
||||
MinWidth="0"
|
||||
Margin="0,-8,0,-8"
|
||||
Padding="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Content="{shcm:ResourceString Name=ViewControlBaseValueSliderPromoted}"
|
||||
IsChecked="{x:Bind BaseValueInfo.Promoted, Mode=TwoWay}"
|
||||
Visibility="{x:Bind IsPromoteVisible, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
<Slider
|
||||
MinWidth="240"
|
||||
Margin="0,-16,0,-16"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="{x:Bind BaseValueInfo.MaxLevel, Mode=OneWay}"
|
||||
Minimum="1"
|
||||
StepFrequency="1"
|
||||
Value="{x:Bind BaseValueInfo.CurrentLevel, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
</cwc:SettingsExpander>
|
||||
|
||||
<ItemsControl
|
||||
VerticalAlignment="Top"
|
||||
ItemTemplate="{StaticResource BaseValueTemplate}"
|
||||
ItemsSource="{x:Bind BaseValueInfo.Values, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -5,35 +5,38 @@
|
||||
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shmm="using:Snap.Hutao.Model.Metadata"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<Thickness x:Key="SettingsCardPadding">16,8</Thickness>
|
||||
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
|
||||
|
||||
<DataTemplate x:Key="ParameterDescriptionTemplate" x:DataType="shmm:ParameterDescription">
|
||||
<cwc:SettingsCard Margin="0,3,0,0" Header="{Binding Description}">
|
||||
<cwc:SettingsCard
|
||||
MinHeight="40"
|
||||
Padding="16,0,42,0"
|
||||
Header="{Binding Description}">
|
||||
<TextBlock Text="{Binding Parameter}"/>
|
||||
</cwc:SettingsCard>
|
||||
</DataTemplate>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel Margin="0,0,0,0" VerticalAlignment="Top">
|
||||
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewControlBaseValueSliderLevel}">
|
||||
<ComboBox
|
||||
DisplayMemberPath="Level"
|
||||
ItemsSource="{x:Bind Source, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind SelectedItem, Mode=TwoWay}"
|
||||
Style="{StaticResource SettingsContentComboBoxStyle}"/>
|
||||
</cwc:SettingsCard>
|
||||
</StackPanel>
|
||||
|
||||
<ItemsControl
|
||||
VerticalAlignment="Top"
|
||||
ItemContainerTransitions="{StaticResource ContentThemeTransitions}"
|
||||
ItemTemplate="{StaticResource ParameterDescriptionTemplate}"
|
||||
ItemsSource="{x:Bind SelectedItem.Parameters, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
<cwc:SettingsExpander
|
||||
Header="{shcm:ResourceString Name=ViewControlBaseValueSliderLevel}"
|
||||
IsExpanded="True"
|
||||
ItemTemplate="{StaticResource ParameterDescriptionTemplate}"
|
||||
ItemsSource="{x:Bind SelectedItem.Parameters, Mode=OneWay}"
|
||||
Visibility="{x:Bind SelectedItem.Parameters.Count, Converter={StaticResource Int32ToVisibilityConverter}, Mode=OneWay}">
|
||||
<shc:SizeRestrictedContentControl Margin="0,-8">
|
||||
<ComboBox
|
||||
x:Name="LevelSelectorComboBox"
|
||||
DisplayMemberPath="Level"
|
||||
SelectionChanged="OnLevelSelectorComboBoxSelectionChanged"
|
||||
Style="{StaticResource SettingsContentComboBoxStyle}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</cwc:SettingsExpander>
|
||||
</UserControl>
|
||||
|
||||
@@ -30,8 +30,16 @@ internal sealed partial class DescParamComboBox : UserControl
|
||||
{
|
||||
if (args.NewValue != args.OldValue && args.NewValue is List<LevelParameters<string, ParameterDescription>> list)
|
||||
{
|
||||
descParamComboBox.SelectedItem = list.ElementAtOrLastOrDefault(descParamComboBox.PreferredSelectedIndex);
|
||||
LevelParameters<string, ParameterDescription>? target = list.ElementAtOrLastOrDefault(descParamComboBox.PreferredSelectedIndex);
|
||||
descParamComboBox.SelectedItem = target;
|
||||
descParamComboBox.LevelSelectorComboBox.ItemsSource = list;
|
||||
descParamComboBox.LevelSelectorComboBox.SelectedItem = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLevelSelectorComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
SelectedItem = (LevelParameters<string, ParameterDescription>)((ComboBox)sender).SelectedItem;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
x:Class="Snap.Hutao.View.Control.SkillPivot"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shch="using:Snap.Hutao.Control.Helper"
|
||||
@@ -17,22 +18,18 @@
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="SkillHeaderTemplate">
|
||||
<StackPanel>
|
||||
<StackPanel Background="Transparent" ToolTipService.ToolTip="{Binding Name}">
|
||||
<shci:MonoChrome shch:FrameworkElementHelper.SquareLength="36" Source="{Binding Icon, Converter={StaticResource SkillIconConverter}}"/>
|
||||
<TextBlock
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Name}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Pivot
|
||||
HeaderTemplate="{StaticResource SkillHeaderTemplate}"
|
||||
ItemTemplate="{x:Bind ItemTemplate}"
|
||||
ItemsSource="{x:Bind Skills, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind Selected}"
|
||||
Style="{StaticResource DefaultPivotStyle}"/>
|
||||
|
||||
<StackPanel>
|
||||
<cwc:Segmented
|
||||
x:Name="SkillSelectorSegmented"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectionChanged="OnSkillSelectorSegmentedSelectionChanged"
|
||||
ItemTemplate="{StaticResource SkillHeaderTemplate}"/>
|
||||
<ContentPresenter Content="{x:Bind Selected, Mode=OneWay}" ContentTemplate="{x:Bind ItemTemplate}"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Collections;
|
||||
@@ -11,7 +12,7 @@ namespace Snap.Hutao.View.Control;
|
||||
/// 技能展柜
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[DependencyProperty("Skills", typeof(IList))]
|
||||
[DependencyProperty("Skills", typeof(IList), null, nameof(OnSkillsChanged))]
|
||||
[DependencyProperty("Selected", typeof(object))]
|
||||
[DependencyProperty("ItemTemplate", typeof(DataTemplate))]
|
||||
internal sealed partial class SkillPivot : UserControl
|
||||
@@ -23,4 +24,22 @@ internal sealed partial class SkillPivot : UserControl
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private static void OnSkillsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (sender is SkillPivot skillPivot)
|
||||
{
|
||||
if (args.OldValue != args.NewValue && args.NewValue as IList is [object target, ..] list)
|
||||
{
|
||||
skillPivot.Selected = target;
|
||||
skillPivot.SkillSelectorSegmented.ItemsSource = list;
|
||||
skillPivot.SkillSelectorSegmented.SelectedItem = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSkillSelectorSegmentedSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
Selected = ((Segmented)sender).SelectedItem;
|
||||
}
|
||||
}
|
||||
@@ -247,13 +247,13 @@
|
||||
</CommandBar.Content>
|
||||
|
||||
<AppBarElementContainer>
|
||||
<ComboBox
|
||||
Height="36"
|
||||
MinWidth="120"
|
||||
Margin="2,6,3,6"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Archives, Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"/>
|
||||
<shc:SizeRestrictedContentControl Margin="2,6,3,6">
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Archives, Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"
|
||||
Style="{ThemeResource CommandBarComboBoxStyle}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</AppBarElementContainer>
|
||||
<AppBarButton
|
||||
Command="{Binding AddArchiveCommand}"
|
||||
|
||||
@@ -248,13 +248,14 @@
|
||||
<Pivot.RightHeader>
|
||||
<CommandBar DefaultLabelPosition="Right">
|
||||
<AppBarElementContainer>
|
||||
<ComboBox
|
||||
Height="36"
|
||||
MinWidth="160"
|
||||
Margin="6,6,6,6"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Projects}"
|
||||
SelectedItem="{Binding SelectedProject, Mode=TwoWay}"/>
|
||||
<shc:SizeRestrictedContentControl Margin="6,6,6,6">
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Projects}"
|
||||
SelectedItem="{Binding SelectedProject, Mode=TwoWay}"
|
||||
Style="{ThemeResource CommandBarComboBoxStyle}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
|
||||
</AppBarElementContainer>
|
||||
<AppBarButton
|
||||
Command="{Binding AddProjectCommand}"
|
||||
|
||||
@@ -231,13 +231,13 @@
|
||||
IsHitTestVisible="False"/>
|
||||
<Pivot>
|
||||
<Pivot.LeftHeader>
|
||||
<ComboBox
|
||||
Height="36"
|
||||
MinWidth="120"
|
||||
Margin="16,6,0,6"
|
||||
DisplayMemberPath="Uid"
|
||||
ItemsSource="{Binding Archives}"
|
||||
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"/>
|
||||
<shc:SizeRestrictedContentControl Margin="16,6,0,6">
|
||||
<ComboBox
|
||||
DisplayMemberPath="Uid"
|
||||
ItemsSource="{Binding Archives}"
|
||||
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"
|
||||
Style="{ThemeResource CommandBarComboBoxStyle}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</Pivot.LeftHeader>
|
||||
<Pivot.RightHeader>
|
||||
<CommandBar DefaultLabelPosition="Right">
|
||||
|
||||
@@ -171,13 +171,14 @@
|
||||
</cwc:SettingsCard.Description>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<shvc:Elevation Margin="0,0,36,0" Visibility="{Binding HutaoOptions.IsElevated, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
|
||||
<shccs:ComboBox2
|
||||
MinWidth="320"
|
||||
DisplayMemberPath="DisplayName"
|
||||
EnableMemberPath="IsNotCompatOnly"
|
||||
ItemsSource="{Binding KnownSchemes}"
|
||||
SelectedItem="{Binding SelectedScheme, Mode=TwoWay}"
|
||||
Style="{StaticResource DefaultComboBoxStyle}"/>
|
||||
<shc:SizeRestrictedContentControl>
|
||||
<shccs:ComboBox2
|
||||
DisplayMemberPath="DisplayName"
|
||||
EnableMemberPath="IsNotCompatOnly"
|
||||
ItemsSource="{Binding KnownSchemes}"
|
||||
SelectedItem="{Binding SelectedScheme, Mode=TwoWay}"
|
||||
Style="{StaticResource DefaultComboBoxStyle}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</StackPanel>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
@@ -217,11 +218,12 @@
|
||||
<ToggleSwitch Width="120" IsOn="{Binding Options.IsBorderless, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioDescription}" Header="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioHeader}">
|
||||
<ComboBox
|
||||
Width="156"
|
||||
Margin="0,0,136,0"
|
||||
ItemsSource="{Binding Options.AspectRatios}"
|
||||
SelectedItem="{Binding Options.SelectedAspectRatio, Mode=TwoWay}"/>
|
||||
<shc:SizeRestrictedContentControl Margin="0,0,136,0">
|
||||
<ComboBox
|
||||
ItemsSource="{Binding Options.AspectRatios}"
|
||||
PlaceholderText="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioPlaceHolder}"
|
||||
SelectedItem="{Binding Options.SelectedAspectRatio, Mode=TwoWay}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceScreenWidthDescription}" Header="-screen-width">
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
@@ -247,13 +249,13 @@
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameMonitorsDescription}" Header="-monitor">
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
<ComboBox
|
||||
Width="156"
|
||||
VerticalAlignment="Center"
|
||||
DisplayMemberPath="Name"
|
||||
IsEnabled="{Binding Options.IsMonitorEnabled}"
|
||||
ItemsSource="{Binding Options.Monitors}"
|
||||
SelectedItem="{Binding Options.Monitor, Mode=TwoWay}"/>
|
||||
<shc:SizeRestrictedContentControl>
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
IsEnabled="{Binding Options.IsMonitorEnabled}"
|
||||
ItemsSource="{Binding Options.Monitors}"
|
||||
SelectedItem="{Binding Options.Monitor, Mode=TwoWay}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
<ToggleSwitch Width="120" IsOn="{Binding Options.IsMonitorEnabled, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
</cwc:SettingsCard>
|
||||
@@ -290,6 +292,12 @@
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding Options.UseStarwardPlayTimeStatistics, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameDiscordActivityDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameDiscordActivityHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding Options.SetDiscordActivityWhenPlaying, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -192,19 +192,23 @@
|
||||
Description="{shcm:ResourceString Name=ViewPageSettingApperanceLanguageDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageSettingApperanceLanguageHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Options.Cultures}"
|
||||
SelectedItem="{Binding SelectedCulture, Mode=TwoWay}"/>
|
||||
<shc:SizeRestrictedContentControl>
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Options.Cultures}"
|
||||
SelectedItem="{Binding SelectedCulture, Mode=TwoWay}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageSettingBackdropMaterialDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageSettingBackdropMaterialHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Options.BackdropTypes}"
|
||||
SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/>
|
||||
<shc:SizeRestrictedContentControl>
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Options.BackdropTypes}"
|
||||
SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingKeyShortcutHeader}"/>
|
||||
@@ -240,13 +244,14 @@
|
||||
Content="Alt"
|
||||
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasAlt, Mode=TwoWay}"/>
|
||||
</cwc:UniformGrid>
|
||||
<ComboBox
|
||||
MinWidth="120"
|
||||
VerticalAlignment="Center"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding HotKeyOptions.VirtualKeys}"
|
||||
SelectedItem="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.KeyNameValue, Mode=TwoWay}"
|
||||
Style="{StaticResource DefaultComboBoxStyle}"/>
|
||||
<shc:SizeRestrictedContentControl>
|
||||
<ComboBox
|
||||
MinWidth="120"
|
||||
VerticalAlignment="Center"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding HotKeyOptions.VirtualKeys}"
|
||||
SelectedItem="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.KeyNameValue, Mode=TwoWay}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
<ToggleSwitch
|
||||
MinWidth="120"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
PlaceholderText="Please input link"
|
||||
Text="{Binding Announcement.Link, Mode=TwoWay}"/>
|
||||
<TextBox
|
||||
Header="Link"
|
||||
Header="Version Threshold"
|
||||
PlaceholderText="Max present version(leave empty to present in any version)"
|
||||
Text="{Binding Announcement.MaxPresentVersion, Mode=TwoWay}"/>
|
||||
<TextBox
|
||||
@@ -124,14 +124,15 @@
|
||||
Header="Content"
|
||||
PlaceholderText="Please input content"
|
||||
Text="{Binding Announcement.Content, Mode=TwoWay}"/>
|
||||
<ComboBox
|
||||
Header="Severity"
|
||||
ItemsSource="{cwh:EnumValues Type=InfoBarSeverity}"
|
||||
SelectedItem="{Binding Announcement.Severity, Mode=TwoWay}"/>
|
||||
<shc:SizeRestrictedContentControl>
|
||||
<ComboBox
|
||||
Header="Severity"
|
||||
ItemsSource="{cwh:EnumValues Type=InfoBarSeverity}"
|
||||
SelectedItem="{Binding Announcement.Severity, Mode=TwoWay}"/>
|
||||
</shc:SizeRestrictedContentControl>
|
||||
<Button Command="{Binding UploadAnnouncementCommand}" Content="Upload"/>
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</shc:ScopedPage>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
xmlns:shcp="using:Snap.Hutao.Control.Panel"
|
||||
xmlns:shct="using:Snap.Hutao.Control.Text"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
xmlns:shvcp="using:Snap.Hutao.View.Card.Primitive"
|
||||
xmlns:shvw="using:Snap.Hutao.ViewModel.Wiki"
|
||||
d:DataContext="{d:DesignInstance Type=shvw:WikiAvatarViewModel}"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
@@ -24,21 +25,24 @@
|
||||
</mxi:Interaction.Behaviors>
|
||||
<Page.Resources>
|
||||
<DataTemplate x:Key="SkillDataTemplate">
|
||||
<Grid Margin="0,12,0,0">
|
||||
<StackPanel Grid.Column="0">
|
||||
<shct:DescriptionTextBlock Margin="0,0,0,16" Description="{Binding Description}">
|
||||
<shct:DescriptionTextBlock.Resources>
|
||||
<Style BasedOn="{StaticResource BodyTextBlockStyle}" TargetType="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
</shct:DescriptionTextBlock.Resources>
|
||||
</shct:DescriptionTextBlock>
|
||||
<shvc:DescParamComboBox
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
PreferredSelectedIndex="9"
|
||||
Source="{Binding Proud, Mode=OneWay, Converter={StaticResource DescParamDescriptor}}"/>
|
||||
</StackPanel>
|
||||
<Grid Margin="0,16,0,0" ColumnSpacing="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="3*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shct:DescriptionTextBlock VerticalAlignment="Top" Description="{Binding Description}">
|
||||
<shct:DescriptionTextBlock.Resources>
|
||||
<Style BasedOn="{StaticResource BodyTextBlockStyle}" TargetType="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
</shct:DescriptionTextBlock.Resources>
|
||||
</shct:DescriptionTextBlock>
|
||||
<shvc:DescParamComboBox
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
PreferredSelectedIndex="9"
|
||||
Source="{Binding Proud, Mode=OneWay, Converter={StaticResource DescParamDescriptor}}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -80,66 +84,110 @@
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="CultivationItemTemplate">
|
||||
<shvc:BottomTextControl Text="{Binding Name}">
|
||||
<shvc:ItemIcon Icon="{Binding Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding RankLevel}"/>
|
||||
</shvc:BottomTextControl>
|
||||
<shvcp:HorizontalCard>
|
||||
<shvcp:HorizontalCard.Left>
|
||||
<shvc:ItemIcon
|
||||
Width="40"
|
||||
Height="40"
|
||||
Icon="{Binding Icon, Converter={StaticResource ItemIconConverter}}"
|
||||
Quality="{Binding RankLevel}"/>
|
||||
</shvcp:HorizontalCard.Left>
|
||||
<shvcp:HorizontalCard.Right>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}"/>
|
||||
</shvcp:HorizontalCard.Right>
|
||||
</shvcp:HorizontalCard>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="CollocationTemplate">
|
||||
<shvc:BottomTextControl Text="{Binding Rate}" ToolTipService.ToolTip="{Binding Name}">
|
||||
<shvc:ItemIcon Icon="{Binding Icon}" Quality="{Binding Quality}"/>
|
||||
</shvc:BottomTextControl>
|
||||
<shvcp:HorizontalCard>
|
||||
<shvcp:HorizontalCard.Left>
|
||||
<shvc:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
</shvcp:HorizontalCard.Left>
|
||||
<shvcp:HorizontalCard.Right>
|
||||
<StackPanel Margin="8,0,0,0" HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
<TextBlock
|
||||
Opacity="0.7"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Rate}"/>
|
||||
</StackPanel>
|
||||
</shvcp:HorizontalCard.Right>
|
||||
</shvcp:HorizontalCard>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="CollocationReliquaryTemplate">
|
||||
<shvc:BottomTextControl Text="{Binding Rate}" ToolTipService.ToolTip="{Binding Name}">
|
||||
<cwc:SwitchPresenter Value="{Binding Icons.Count, Mode=OneWay}">
|
||||
<cwc:Case IsDefault="True">
|
||||
<cwc:Case.Value>
|
||||
<x:Int32>0</x:Int32>
|
||||
</cwc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon
|
||||
Width="80"
|
||||
Height="80"
|
||||
Icon="{StaticResource UI_ItemIcon_None}"
|
||||
Quality="QUALITY_ORANGE"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case>
|
||||
<cwc:Case.Value>
|
||||
<x:Int32>1</x:Int32>
|
||||
</cwc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon
|
||||
Width="80"
|
||||
Height="80"
|
||||
Icon="{Binding Icons[0]}"
|
||||
Quality="QUALITY_ORANGE"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case>
|
||||
<cwc:Case.Value>
|
||||
<x:Int32>2</x:Int32>
|
||||
</cwc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon Quality="QUALITY_ORANGE"/>
|
||||
<shci:CachedImage
|
||||
Width="54"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Icons[0]}"/>
|
||||
<shci:CachedImage
|
||||
Width="54"
|
||||
Margin="0,0,0,4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Source="{Binding Icons[1]}"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
</cwc:SwitchPresenter>
|
||||
</shvc:BottomTextControl>
|
||||
<shvcp:HorizontalCard>
|
||||
<shvcp:HorizontalCard.Left>
|
||||
<cwc:SwitchPresenter Value="{Binding Icons.Count, Mode=OneWay}">
|
||||
<cwc:Case IsDefault="True">
|
||||
<cwc:Case.Value>
|
||||
<x:Int32>0</x:Int32>
|
||||
</cwc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Icon="{StaticResource UI_ItemIcon_None}"
|
||||
Quality="QUALITY_ORANGE"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case>
|
||||
<cwc:Case.Value>
|
||||
<x:Int32>1</x:Int32>
|
||||
</cwc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Icon="{Binding Icons[0]}"
|
||||
Quality="QUALITY_ORANGE"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case>
|
||||
<cwc:Case.Value>
|
||||
<x:Int32>2</x:Int32>
|
||||
</cwc:Case.Value>
|
||||
<Grid>
|
||||
<shvc:ItemIcon
|
||||
Width="48"
|
||||
Height="48"
|
||||
Quality="QUALITY_ORANGE"/>
|
||||
<shci:CachedImage
|
||||
Width="32"
|
||||
Margin="2,2,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Icons[0]}"/>
|
||||
<shci:CachedImage
|
||||
Width="32"
|
||||
Margin="0,0,2,2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Source="{Binding Icons[1]}"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
</cwc:SwitchPresenter>
|
||||
</shvcp:HorizontalCard.Left>
|
||||
<shvcp:HorizontalCard.Right>
|
||||
<StackPanel Margin="8,0,8,0" HorizontalAlignment="Left">
|
||||
<TextBlock
|
||||
Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock
|
||||
Opacity="0.7"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Rate}"/>
|
||||
</StackPanel>
|
||||
</shvcp:HorizontalCard.Right>
|
||||
</shvcp:HorizontalCard>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="CostumeTemplate">
|
||||
@@ -169,14 +217,11 @@
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="FetterStoryTemplate">
|
||||
<StackPanel Margin="0,0,0,0">
|
||||
<TextBlock Text="{Binding Title}"/>
|
||||
<shct:DescriptionTextBlock
|
||||
Margin="0,8,0,0"
|
||||
Description="{Binding Context}"
|
||||
TextStyle="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<MenuFlyoutSeparator Margin="0,8"/>
|
||||
</StackPanel>
|
||||
<cwc:SettingsCard Padding="16,8,8,8" Header="{Binding Title}">
|
||||
<cwc:SettingsCard.Description>
|
||||
<shct:DescriptionTextBlock Description="{Binding Context}" TextStyle="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</cwc:SettingsCard.Description>
|
||||
</cwc:SettingsCard>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="AvatarGridTemplate">
|
||||
@@ -194,7 +239,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<CommandBar
|
||||
Grid.Row="0"
|
||||
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{x:Null}"
|
||||
BorderThickness="0"
|
||||
DefaultLabelPosition="Right">
|
||||
@@ -239,7 +284,7 @@
|
||||
DisplayMode="Inline"
|
||||
IsPaneOpen="True"
|
||||
OpenPaneLength="{StaticResource CompatSplitViewOpenPaneLength}"
|
||||
PaneBackground="{StaticResource CardBackgroundFillColorSecondaryBrush}">
|
||||
PaneBackground="{ThemeResource CardBackgroundFillColorSecondaryBrush}">
|
||||
<SplitView.Pane>
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
@@ -264,315 +309,392 @@
|
||||
<ScrollViewer>
|
||||
<StackPanel
|
||||
MaxWidth="800"
|
||||
Margin="0,0,16,16"
|
||||
HorizontalAlignment="Left">
|
||||
Padding="32"
|
||||
HorizontalAlignment="Left"
|
||||
Spacing="16">
|
||||
<!-- 简介 -->
|
||||
<Grid Margin="16,16,0,16" VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<Grid Margin="0,0,0,16">
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Grid Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}" Style="{ThemeResource GridCardStyle}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Margin="16">
|
||||
<Grid Margin="0,0,0,12" ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shci:MonoChrome
|
||||
Grid.Column="0"
|
||||
Width="32"
|
||||
Height="32"
|
||||
HorizontalAlignment="Left"
|
||||
Source="{Binding Selected.FetterInfo.VisionBefore, Converter={StaticResource ElementNameIconConverter}}"/>
|
||||
<shci:MonoChrome
|
||||
Grid.Column="1"
|
||||
Width="32"
|
||||
Height="32"
|
||||
Source="{Binding Selected.Weapon, Converter={StaticResource WeaponTypeIconConverter}}"/>
|
||||
</Grid>
|
||||
<shvc:ItemIcon
|
||||
Width="128"
|
||||
Height="128"
|
||||
Icon="{Binding Selected.Icon, Converter={StaticResource AvatarIconConverter}, Mode=OneWay}"
|
||||
Quality="{Binding Selected.Quality, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Margin="16">
|
||||
<StackPanel Margin="0,0,0,2" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding Selected.Name}"/>
|
||||
<TextBlock
|
||||
Margin="24,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.Title}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Opacity="0.7"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.Detail}"
|
||||
TextWrapping="NoWrap"/>
|
||||
|
||||
<cwc:UniformGrid
|
||||
Margin="0,16,0,0"
|
||||
ColumnSpacing="6"
|
||||
Columns="4">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarOccupationNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.Native}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarConstellationNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.ConstellationBefore}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarDateofBirthTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.BirthFormatted}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
</cwc:UniformGrid>
|
||||
|
||||
<cwc:UniformGrid
|
||||
Margin="0,12,0,0"
|
||||
ColumnSpacing="6"
|
||||
Columns="4">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarChineseCVNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CvChinese}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarJapaneseCVNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CvJapanese}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarEnglishCVNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CvEnglish}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarKoreanCVNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CvKorean}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
</cwc:UniformGrid>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.ColumnSpan="2">
|
||||
<ItemsControl
|
||||
Margin="16,0,16,16"
|
||||
ItemTemplate="{StaticResource CultivationItemTemplate}"
|
||||
ItemsSource="{Binding Selected.CultivationItemsView}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwc:UniformGrid
|
||||
ColumnSpacing="8"
|
||||
Columns="3"
|
||||
RowSpacing="8"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 属性 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}" CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Border.Resources>
|
||||
<SolidColorBrush x:Key="ToggleButtonBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ExpanderContentBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundDisabled" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundPointerOver" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundPressed" Color="Transparent"/>
|
||||
</Border.Resources>
|
||||
<shvc:BaseValueSlider
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
BaseValueInfo="{Binding BaseValueInfo, Mode=OneWay}"/>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- 天赋 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border
|
||||
Padding="16"
|
||||
Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<shvc:SkillPivot ItemTemplate="{StaticResource SkillDataTemplate}" Skills="{Binding Selected.SkillDepot.CompositeSkills}"/>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- 命座 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border
|
||||
Padding="16"
|
||||
Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<shvc:SkillPivot ItemTemplate="{StaticResource TalentDataTemplate}" Skills="{Binding Selected.SkillDepot.Talents}"/>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- 搭配 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border
|
||||
Padding="16"
|
||||
Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Grid ColumnSpacing="8" RowSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shci:MonoChrome
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Width="32"
|
||||
Height="32"
|
||||
Source="{Binding Selected.FetterInfo.VisionBefore, Converter={StaticResource ElementNameIconConverter}}"/>
|
||||
<shci:MonoChrome
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarTeamCombinationHeader}"/>
|
||||
<ItemsControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
ItemTemplate="{StaticResource CollocationTemplate}"
|
||||
ItemsPanel="{StaticResource StackPanelSpacing4Template}"
|
||||
ItemsSource="{Binding Selected.Collocation.Avatars}"/>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="32"
|
||||
Height="32"
|
||||
Source="{Binding Selected.Weapon, Converter={StaticResource WeaponTypeIconConverter}}"/>
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarWeaponCombinationHeader}"/>
|
||||
<ItemsControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
ItemTemplate="{StaticResource CollocationTemplate}"
|
||||
ItemsPanel="{StaticResource StackPanelSpacing4Template}"
|
||||
ItemsSource="{Binding Selected.Collocation.Weapons}"/>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarArtifactSetCombinationHeader}"/>
|
||||
<ItemsControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
ItemTemplate="{StaticResource CollocationReliquaryTemplate}"
|
||||
ItemsPanel="{StaticResource StackPanelSpacing4Template}"
|
||||
ItemsSource="{Binding Selected.Collocation.ReliquarySets}"/>
|
||||
</Grid>
|
||||
<shvc:ItemIcon
|
||||
Width="100"
|
||||
Height="100"
|
||||
Icon="{Binding Selected.Icon, Converter={StaticResource AvatarIconConverter}, Mode=OneWay}"
|
||||
Quality="{Binding Selected.Quality, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="1" Margin="16,0,0,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding Selected.Name}"/>
|
||||
<TextBlock
|
||||
Margin="24,0,0,2"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.Title}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.Detail}"
|
||||
TextWrapping="NoWrap"/>
|
||||
|
||||
<cwc:UniformGrid
|
||||
Margin="0,12,0,0"
|
||||
ColumnSpacing="12"
|
||||
Columns="4">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarOccupationNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.Native}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarConstellationNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.ConstellationBefore}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarDateofBirthTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.BirthFormatted}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
</cwc:UniformGrid>
|
||||
|
||||
<cwc:UniformGrid
|
||||
Margin="0,12,0,0"
|
||||
ColumnSpacing="12"
|
||||
Columns="4">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarChineseCVNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CvChinese}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarJapaneseCVNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CvJapanese}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarEnglishCVNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CvEnglish}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageWiKiAvatarKoreanCVNameTitle}"/>
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.CvKorean}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
</cwc:UniformGrid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- 属性 -->
|
||||
<shvc:BaseValueSlider
|
||||
Margin="16,16,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
BaseValueInfo="{Binding BaseValueInfo, Mode=OneWay}"/>
|
||||
<TextBlock
|
||||
Margin="16,32,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarAscensionMaterialsHeader}"/>
|
||||
<GridView
|
||||
Margin="16,16,0,0"
|
||||
ItemTemplate="{StaticResource CultivationItemTemplate}"
|
||||
ItemsSource="{Binding Selected.CultivationItemsView}"
|
||||
SelectionMode="None"/>
|
||||
|
||||
<TextBlock
|
||||
Margin="16,32,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarTanlentHeader}"/>
|
||||
<shvc:SkillPivot
|
||||
Margin="16,16,0,0"
|
||||
ItemTemplate="{StaticResource SkillDataTemplate}"
|
||||
Skills="{Binding Selected.SkillDepot.CompositeSkills}"/>
|
||||
|
||||
<TextBlock
|
||||
Margin="16,32,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarConstellationNameTitle}"/>
|
||||
<shvc:SkillPivot
|
||||
Grid.Column="1"
|
||||
Margin="16,16,0,0"
|
||||
ItemTemplate="{StaticResource TalentDataTemplate}"
|
||||
Skills="{Binding Selected.SkillDepot.Talents}"/>
|
||||
|
||||
<TextBlock
|
||||
Margin="16,32,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarTeamCombinationHeader}"/>
|
||||
<GridView
|
||||
Margin="16,16,0,0"
|
||||
ItemTemplate="{StaticResource CollocationTemplate}"
|
||||
ItemsSource="{Binding Selected.Collocation.Avatars}"
|
||||
SelectionMode="None"/>
|
||||
<TextBlock
|
||||
Margin="16,0,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarWeaponCombinationHeader}"/>
|
||||
<GridView
|
||||
Margin="16,16,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ItemTemplate="{StaticResource CollocationTemplate}"
|
||||
ItemsSource="{Binding Selected.Collocation.Weapons}"
|
||||
SelectionMode="None"/>
|
||||
<TextBlock
|
||||
Margin="16,0,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarArtifactSetCombinationHeader}"/>
|
||||
<GridView
|
||||
Margin="16,16,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ItemTemplate="{StaticResource CollocationReliquaryTemplate}"
|
||||
ItemsSource="{Binding Selected.Collocation.ReliquarySets}"
|
||||
SelectionMode="None"/>
|
||||
|
||||
<TextBlock
|
||||
Margin="16,32,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarMiscHeader}"/>
|
||||
<Border Margin="16,16,0,0" cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 立绘 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<cwc:ConstrainedBox AspectRatio="2048:1024">
|
||||
<Grid Style="{ThemeResource GridCardStyle}">
|
||||
<Grid Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}" Style="{ThemeResource GridCardStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- 使其有较低的ZOrder -->
|
||||
<shci:CachedImage Grid.ColumnSpan="2" Source="{Binding Selected.Icon, Converter={StaticResource GachaAvatarImgConverter}}"/>
|
||||
|
||||
<shci:CachedImage
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
Source="{Binding Selected.Icon, Converter={StaticResource GachaAvatarImgConverter}}"/>
|
||||
|
||||
<Border Margin="16" Style="{StaticResource BorderCardStyle}">
|
||||
<shci:CachedImage
|
||||
HorizontalAlignment="Stretch"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
Source="{Binding Selected.Icon, Converter={StaticResource GachaAvatarIconConverter}}"/>
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Margin="16"
|
||||
Style="{StaticResource BorderCardStyle}">
|
||||
<cwc:ConstrainedBox AspectRatio="320:1024">
|
||||
<shci:CachedImage CornerRadius="{ThemeResource ControlCornerRadius}" Source="{Binding Selected.Icon, Converter={StaticResource GachaAvatarIconConverter}}"/>
|
||||
</cwc:ConstrainedBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</cwc:ConstrainedBox>
|
||||
</Border>
|
||||
|
||||
|
||||
<!-- 料理 -->
|
||||
<Expander
|
||||
Margin="16,16,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="{shcm:ResourceString Name=ViewPageWiKiAvatarFoodHeader}">
|
||||
<Grid DataContext="{Binding Selected.CookBonusView}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarSpecialFoodTitle}"/>
|
||||
<shvc:BottomTextControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,16,0,0"
|
||||
Text="{Binding Item.Name}">
|
||||
<shvc:ItemIcon Icon="{Binding Item.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding Item.RankLevel}"/>
|
||||
</shvc:BottomTextControl>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarOriginalFoodTitle}"/>
|
||||
<shvc:BottomTextControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="16,16,0,0"
|
||||
Text="{Binding OriginItem.Name}">
|
||||
<shvc:ItemIcon Icon="{Binding OriginItem.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding OriginItem.RankLevel}"/>
|
||||
</shvc:BottomTextControl>
|
||||
<StackPanel
|
||||
Grid.RowSpan="4"
|
||||
Grid.Column="2"
|
||||
Margin="16,0,0,0">
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="4"
|
||||
Text="{Binding Item.Description}"
|
||||
TextWrapping="Wrap"/>
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,16,0,0"
|
||||
Text="{Binding Item.EffectDescription}"
|
||||
TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Expander>
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}" CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Border.Resources>
|
||||
<SolidColorBrush x:Key="ExpanderContentBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBackground" Color="Transparent"/>
|
||||
</Border.Resources>
|
||||
<Expander
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="{shcm:ResourceString Name=ViewPageWiKiAvatarFoodHeader}"
|
||||
IsExpanded="True">
|
||||
<Grid DataContext="{Binding Selected.CookBonusView}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarSpecialFoodTitle}"/>
|
||||
<shvc:BottomTextControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,16,0,0"
|
||||
Text="{Binding Item.Name}">
|
||||
<shvc:ItemIcon Icon="{Binding Item.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding Item.RankLevel}"/>
|
||||
</shvc:BottomTextControl>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarOriginalFoodTitle}"/>
|
||||
<shvc:BottomTextControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="16,16,0,0"
|
||||
Text="{Binding OriginItem.Name}">
|
||||
<shvc:ItemIcon Icon="{Binding OriginItem.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding OriginItem.RankLevel}"/>
|
||||
</shvc:BottomTextControl>
|
||||
<StackPanel
|
||||
Grid.RowSpan="4"
|
||||
Grid.Column="2"
|
||||
Margin="16,0,0,0">
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="4"
|
||||
Text="{Binding Item.Description}"
|
||||
TextWrapping="Wrap"/>
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,16,0,0"
|
||||
Text="{Binding Item.EffectDescription}"
|
||||
TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Expander>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- 衣装 -->
|
||||
<Expander
|
||||
Margin="16,16,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="{shcm:ResourceString Name=ViewPageWiKiAvatarCostumeHeader}">
|
||||
<ItemsControl
|
||||
Margin="0,0,0,-16"
|
||||
ItemTemplate="{StaticResource CostumeTemplate}"
|
||||
ItemsSource="{Binding Selected.Costumes}"/>
|
||||
</Expander>
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}" CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Border.Resources>
|
||||
<SolidColorBrush x:Key="ExpanderContentBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBackground" Color="Transparent"/>
|
||||
</Border.Resources>
|
||||
<Expander
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="{shcm:ResourceString Name=ViewPageWiKiAvatarCostumeHeader}"
|
||||
IsExpanded="True">
|
||||
<ItemsControl
|
||||
Margin="0,0,0,-16"
|
||||
ItemTemplate="{StaticResource CostumeTemplate}"
|
||||
ItemsSource="{Binding Selected.Costumes}"/>
|
||||
</Expander>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- 资料 -->
|
||||
<Expander
|
||||
Margin="16,16,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="{shcm:ResourceString Name=ViewPageWiKiAvatarQuotesHeader}">
|
||||
<ItemsControl
|
||||
Margin="0,0,0,-24"
|
||||
ItemTemplate="{StaticResource FetterStoryTemplate}"
|
||||
ItemsSource="{Binding Selected.FetterInfo.Fetters}"/>
|
||||
</Expander>
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}" CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Border.Resources>
|
||||
<SolidColorBrush x:Key="ToggleButtonBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ExpanderContentBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundDisabled" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundPointerOver" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundPressed" Color="Transparent"/>
|
||||
</Border.Resources>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Hidden">
|
||||
<cwc:SettingsExpander
|
||||
Header="{shcm:ResourceString Name=ViewPageWiKiAvatarQuotesHeader}"
|
||||
ItemTemplate="{StaticResource FetterStoryTemplate}"
|
||||
ItemsSource="{Binding Selected.FetterInfo.Fetters}"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- 故事 -->
|
||||
<Expander
|
||||
Margin="16,16,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Header="{shcm:ResourceString Name=ViewPageWiKiAvatarStoriesHeader}">
|
||||
<ItemsControl
|
||||
Margin="0,0,0,-24"
|
||||
ItemTemplate="{StaticResource FetterStoryTemplate}"
|
||||
ItemsSource="{Binding Selected.FetterInfo.FetterStories}"/>
|
||||
</Expander>
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}" CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Border.Resources>
|
||||
<SolidColorBrush x:Key="ToggleButtonBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ExpanderContentBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackground" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundDisabled" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundPointerOver" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="SettingsCardBackgroundPressed" Color="Transparent"/>
|
||||
</Border.Resources>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Hidden">
|
||||
<cwc:SettingsExpander
|
||||
Header="{shcm:ResourceString Name=ViewPageWiKiAvatarStoriesHeader}"
|
||||
ItemTemplate="{StaticResource FetterStoryTemplate}"
|
||||
ItemsSource="{Binding Selected.FetterInfo.FetterStories}"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
@@ -32,18 +32,23 @@
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{x:Bind HotKeyOptions.IsMouseClickRepeatForeverOn, Mode=OneWay}"
|
||||
IsHitTestVisible="False"
|
||||
Visibility="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.IsEnabled, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
Visibility="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.IsEnabled, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0" Text="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.DisplayName, Mode=OneWay}"/>
|
||||
<TextBlock Grid.Column="1" Text="{shcm:ResourceString Name=ViewTitleAutoClicking}"/>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{shcm:ResourceString Name=ViewTitleAutoClicking}"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.DisplayName, Mode=OneWay}"/>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -107,6 +107,14 @@
|
||||
Text="{Binding SelectedUser.UserInfo.Nickname, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="1,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{shcm:ResourceString Name=ViewUserNoUserHint}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{Binding Users.Count, Converter={StaticResource Int32ToVisibilityRevertConverter}}"/>
|
||||
<FontIcon
|
||||
Grid.Column="2"
|
||||
Margin="0,0,8,0"
|
||||
|
||||
@@ -66,7 +66,7 @@ internal sealed partial class AchievementImporter
|
||||
{
|
||||
ValueResult<bool, ValueFile> pickerResult = fileSystemPickerInteraction.PickFile(
|
||||
SH.ServiceAchievementUIAFImportPickerTitile,
|
||||
[(SH.ServiceAchievementUIAFImportPickerFilterText, ".json")]);
|
||||
[(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]);
|
||||
|
||||
if (pickerResult.TryGetValue(out ValueFile file))
|
||||
{
|
||||
|
||||
@@ -27,20 +27,14 @@ internal sealed class ReliquarySetView
|
||||
{
|
||||
StringBuilder nameBuilder = new();
|
||||
List<Uri> icons = new(2);
|
||||
foreach (ReliquarySet set in CollectionsMarshal.AsSpan(sets))
|
||||
foreach (ref readonly ReliquarySet set in CollectionsMarshal.AsSpan(sets))
|
||||
{
|
||||
Model.Metadata.Reliquary.ReliquarySet metaSet = idReliquarySetMap[set.EquipAffixId];
|
||||
|
||||
if (nameBuilder.Length > 0)
|
||||
{
|
||||
nameBuilder.AppendLine();
|
||||
}
|
||||
|
||||
nameBuilder.Append(set.Count).Append('×').Append(metaSet.Name);
|
||||
nameBuilder.Append(set.Count).Append('×').Append(metaSet.Name).Append('+');
|
||||
icons.Add(RelicIconConverter.IconNameToUri(metaSet.Icon));
|
||||
}
|
||||
|
||||
Name = nameBuilder.ToString();
|
||||
Name = nameBuilder.ToString(0, nameBuilder.Length - 1);
|
||||
Icons = icons;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -128,7 +128,7 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
|
||||
[Command("NavigateToSpiralAbyssRecordCommand")]
|
||||
private void NavigateToSpiralAbyssRecord()
|
||||
{
|
||||
navigationService.Navigate<View.Page.SpiralAbyssRecordPage>(INavigationAwaiter.Default);
|
||||
navigationService.Navigate<View.Page.SpiralAbyssRecordPage>(INavigationAwaiter.Default, true);
|
||||
}
|
||||
|
||||
private async ValueTask RefreshUidCollectionAsync()
|
||||
|
||||
@@ -46,30 +46,30 @@ internal static class StaticResource
|
||||
private static readonly ApplicationDataCompositeValue LatestResourceVersionMap = new()
|
||||
{
|
||||
{ "AchievementIcon", 1 },
|
||||
{ "AvatarCard", 0 },
|
||||
{ "AvatarIcon", 2 },
|
||||
{ "Bg", 1 },
|
||||
{ "ChapterIcon", 0 },
|
||||
{ "AvatarCard", 1 },
|
||||
{ "AvatarIcon", 3 },
|
||||
{ "Bg", 2 },
|
||||
{ "ChapterIcon", 1 },
|
||||
{ "CodexMonster", 0 },
|
||||
{ "Costume", 0 },
|
||||
{ "EmotionIcon", 0 },
|
||||
{ "EquipIcon", 1 },
|
||||
{ "GachaAvatarIcon", 1 },
|
||||
{ "GachaAvatarImg", 1 },
|
||||
{ "GachaEquipIcon", 1 },
|
||||
{ "Costume", 1 },
|
||||
{ "EmotionIcon", 1 },
|
||||
{ "EquipIcon", 2 },
|
||||
{ "GachaAvatarIcon", 2 },
|
||||
{ "GachaAvatarImg", 2 },
|
||||
{ "GachaEquipIcon", 2 },
|
||||
{ "GcgCharAvatarIcon", 0 },
|
||||
{ "IconElement", 1 },
|
||||
{ "ItemIcon", 1 },
|
||||
{ "LoadingPic", 0 },
|
||||
{ "MonsterIcon", 1 },
|
||||
{ "MonsterSmallIcon", 0 },
|
||||
{ "NameCardIcon", 0 },
|
||||
{ "NameCardPic", 1 },
|
||||
{ "IconElement", 2 },
|
||||
{ "ItemIcon", 2 },
|
||||
{ "LoadingPic", 1 },
|
||||
{ "MonsterIcon", 2 },
|
||||
{ "MonsterSmallIcon", 1 },
|
||||
{ "NameCardIcon", 1 },
|
||||
{ "NameCardPic", 2 },
|
||||
{ "NameCardPicAlpha", 0 },
|
||||
{ "Property", 1 },
|
||||
{ "RelicIcon", 1 },
|
||||
{ "Skill", 1 },
|
||||
{ "Talent", 1 },
|
||||
{ "RelicIcon", 2 },
|
||||
{ "Skill", 2 },
|
||||
{ "Talent", 2 },
|
||||
};
|
||||
|
||||
public static void FulfillAll()
|
||||
|
||||
@@ -7,4 +7,6 @@ internal sealed class LatestPackage : Package
|
||||
{
|
||||
[JsonPropertyName("segments")]
|
||||
public List<PackageSegment> Segments { get; set; } = default!;
|
||||
|
||||
public new string DisplayName { get => Name; }
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||
|
||||
@@ -46,8 +47,15 @@ internal sealed partial class ResourceClient
|
||||
// 补全缺失的信息
|
||||
if (resp is { Data.Game.Latest: LatestPackage latest })
|
||||
{
|
||||
latest.Path = latest.Segments[0].Path[..^4]; // .00X
|
||||
latest.Name = Path.GetFileName(latest.Path);
|
||||
StringBuilder pathBuilder = new();
|
||||
foreach (PackageSegment segment in latest.Segments)
|
||||
{
|
||||
pathBuilder.AppendLine(segment.Path);
|
||||
}
|
||||
|
||||
latest.Path = pathBuilder.ToStringTrimEndReturn();
|
||||
string path = latest.Segments[0].Path[..^4]; // .00X
|
||||
latest.Name = Path.GetFileName(path);
|
||||
}
|
||||
|
||||
return Response.Response.DefaultIfNull(resp);
|
||||
|
||||
@@ -34,7 +34,7 @@ internal sealed partial class CardClient
|
||||
public async ValueTask<Response<VerificationRegistration>> CreateVerificationAsync(User user, CancellationToken token)
|
||||
{
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.CardCreateVerification(false))
|
||||
.SetRequestUri(ApiEndpoints.CardCreateVerification(true))
|
||||
.SetUserCookieAndFpHeader(user, CookieType.LToken)
|
||||
.Get();
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.GameRecordDailyNote(userAndUid.Uid))
|
||||
.SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie)
|
||||
.SetReferer(ApiEndpoints.WebStaticMihoyoReferer)
|
||||
.Get();
|
||||
|
||||
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
|
||||
@@ -60,6 +61,7 @@ internal sealed partial class GameRecordClient : IGameRecordClient
|
||||
HttpRequestMessageBuilder verifiedbuilder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.GameRecordDailyNote(userAndUid.Uid))
|
||||
.SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie)
|
||||
.SetReferer(ApiEndpoints.WebStaticMihoyoReferer)
|
||||
.SetXrpcChallenge(challenge)
|
||||
.Get();
|
||||
|
||||
@@ -86,6 +88,8 @@ internal sealed partial class GameRecordClient : IGameRecordClient
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.GameRecordIndex(userAndUid.Uid))
|
||||
.SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie)
|
||||
.SetHeader("x-rpc-page", "v4.2.2-ys_#/ys/daily")
|
||||
.SetReferer(ApiEndpoints.WebStaticMihoyoReferer)
|
||||
.Get();
|
||||
|
||||
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
|
||||
@@ -106,6 +110,8 @@ internal sealed partial class GameRecordClient : IGameRecordClient
|
||||
HttpRequestMessageBuilder verifiedbuilder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.GameRecordIndex(userAndUid.Uid))
|
||||
.SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie)
|
||||
.SetHeader("x-rpc-page", "v4.2.2-ys_#/ys/daily")
|
||||
.SetReferer(ApiEndpoints.WebStaticMihoyoReferer)
|
||||
.SetXrpcChallenge(challenge)
|
||||
.Get();
|
||||
|
||||
@@ -133,6 +139,8 @@ internal sealed partial class GameRecordClient : IGameRecordClient
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.GameRecordSpiralAbyss(schedule, userAndUid.Uid))
|
||||
.SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie)
|
||||
.SetHeader("x-rpc-page", "v4.2.2-ys_#/ys/daily")
|
||||
.SetReferer(ApiEndpoints.WebStaticMihoyoReferer)
|
||||
.Get();
|
||||
|
||||
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
|
||||
@@ -153,6 +161,8 @@ internal sealed partial class GameRecordClient : IGameRecordClient
|
||||
HttpRequestMessageBuilder verifiedbuilder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.GameRecordSpiralAbyss(schedule, userAndUid.Uid))
|
||||
.SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie)
|
||||
.SetHeader("x-rpc-page", "v4.2.2-ys_#/ys/daily")
|
||||
.SetReferer(ApiEndpoints.WebStaticMihoyoReferer)
|
||||
.SetXrpcChallenge(challenge)
|
||||
.Get();
|
||||
|
||||
@@ -179,6 +189,8 @@ internal sealed partial class GameRecordClient : IGameRecordClient
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.GameRecordRoleBasicInfo(userAndUid.Uid))
|
||||
.SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie)
|
||||
.SetHeader("x-rpc-page", "v4.2.2-ys_#/ys/daily")
|
||||
.SetReferer(ApiEndpoints.WebStaticMihoyoReferer)
|
||||
.Get();
|
||||
|
||||
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
|
||||
@@ -203,6 +215,8 @@ internal sealed partial class GameRecordClient : IGameRecordClient
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.GameRecordCharacter)
|
||||
.SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie)
|
||||
.SetHeader("x-rpc-page", "v4.2.2-ys_#/ys/daily")
|
||||
.SetReferer(ApiEndpoints.WebStaticMihoyoReferer)
|
||||
.PostJson(new CharacterData(userAndUid.Uid, playerInfo.Avatars.Select(x => x.Id)));
|
||||
|
||||
await builder.SignDataAsync(DataSignAlgorithmVersion.Gen2, SaltType.X4, false).ConfigureAwait(false);
|
||||
@@ -223,6 +237,8 @@ internal sealed partial class GameRecordClient : IGameRecordClient
|
||||
HttpRequestMessageBuilder verifiedBuilder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(ApiEndpoints.GameRecordCharacter)
|
||||
.SetUserCookieAndFpHeader(userAndUid, CookieType.Cookie)
|
||||
.SetHeader("x-rpc-page", "v4.2.2-ys_#/ys/daily")
|
||||
.SetReferer(ApiEndpoints.WebStaticMihoyoReferer)
|
||||
.SetXrpcChallenge(challenge)
|
||||
.PostJson(new CharacterData(userAndUid.Uid, playerInfo.Avatars.Select(x => x.Id)));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user