Compare commits

..

84 Commits

Author SHA1 Message Date
qhy040404
13108b6694 show finished icon if we have enough items 2024-03-27 16:23:30 +08:00
DismissedLight
ab1b3c02c8 Merge pull request #1500 from DGP-Studio/feat/suggestbox_not_found 2024-03-25 22:43:47 +08:00
DismissedLight
8ef5dc9302 code style 2024-03-25 22:41:58 +08:00
qhy040404
a387e0dbf5 fix achievement search reset 2024-03-23 12:59:38 +08:00
qhy040404
8111c1e662 Disable unknown token 2024-03-23 12:58:49 +08:00
qhy040404
9cd9193425 show not found in view 2024-03-23 12:58:49 +08:00
qhy040404
d531c81fa2 show not found in search box 2024-03-23 12:58:49 +08:00
DismissedLight
1d0a72493e achievement city typedef 2024-03-21 22:27:16 +08:00
Lightczx
f2b361819b fix userview 2024-03-20 10:42:14 +08:00
qhy040404
41f7245a1a fix #1503 2024-03-19 22:46:58 +08:00
DismissedLight
889e914f7f colorized log message 2024-03-19 22:23:47 +08:00
Lightczx
9f90ec221c colorized 2024-03-19 17:30:23 +08:00
DismissedLight
8fc874fd09 Merge pull request #1498 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-f9ad2d8712 2024-03-19 15:13:47 +08:00
Lightczx
f42ec1ea12 change banner color 2024-03-18 17:25:45 +08:00
Lightczx
5cc3cf264c refactor 2024-03-18 17:15:42 +08:00
Lightczx
e38517a2ad optimize uniformpanel 2024-03-18 16:54:17 +08:00
dependabot[bot]
cdc0fb8a82 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [coverlet.collector](https://github.com/coverlet-coverage/coverlet).


Updates `coverlet.collector` from 6.0.1 to 6.0.2
- [Release notes](https://github.com/coverlet-coverage/coverlet/releases)
- [Commits](https://github.com/coverlet-coverage/coverlet/compare/v6.0.1...v6.0.2)

---
updated-dependencies:
- dependency-name: coverlet.collector
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 07:31:21 +00:00
ema
d50a6df14c upgrade SH.ja.resx translation (#1491) 2024-03-16 17:06:34 +08:00
DismissedLight
4886904530 refactor controls 2024-03-16 16:59:19 +08:00
DismissedLight
ac34376c13 Merge pull request #1484 from DGP-Studio/feat/spinwait_game_window 2024-03-16 16:57:17 +08:00
qhy040404
3bcee3d149 Apply suggestion 2024-03-16 16:05:18 +08:00
qhy040404
3d3b03851e Use SpinWait to wait game window instead of WaitForInputIdle 2024-03-16 15:59:38 +08:00
DismissedLight
dc64302424 Merge pull request #1483 from DGP-Studio/fix/theme 2024-03-16 14:57:54 +08:00
qhy040404
a2586b0ef2 fix different theme between window and dialog 2024-03-15 20:32:16 +08:00
DismissedLight
eee84a338e significantly reduce uniformstaggeredlayout resize lag 2024-03-15 20:24:32 +08:00
Lightczx
be30362b52 uniformstaggeredlayout optimization 2024-03-15 17:18:25 +08:00
Lightczx
38f36bbb82 fix gachalog page closing crash 2024-03-15 14:21:38 +08:00
Lightczx
704866b16a update dependency 2024-03-15 13:43:55 +08:00
Lightczx
ca9783bc1b corner radius on auto suggest tokenbox 2024-03-15 10:54:06 +08:00
DismissedLight
6e8e151fff fix unlocking fps 2024-03-14 20:32:06 +08:00
DismissedLight
b98dc9f5d3 fix unlocking fps 2024-03-14 19:58:01 +08:00
Masterain
206100d8ef New Crowdin updates (#1477) 2024-03-14 14:41:52 +08:00
Lightczx
1a74c7ca96 fix #1476 2024-03-14 11:24:55 +08:00
DismissedLight
88528fa28d hide zero count section 2024-03-13 21:18:11 +08:00
DismissedLight
263cea9225 Merge pull request #1475 from DGP-Studio/fix/token_ui 2024-03-13 21:09:13 +08:00
Lightczx
2879bd653a add chronicled wish cloud statistics 2024-03-13 17:25:42 +08:00
qhy040404
3d061e3bdb adjust ui 2024-03-13 16:52:46 +08:00
qhy040404
022527829e refine wiki token ui 2 2024-03-13 16:39:16 +08:00
qhy040404
0fcf10dfa7 refine wiki token ui 2024-03-13 16:16:44 +08:00
Lightczx
7fc6ecc3c3 fix gachalog overview scrollbar margin 2024-03-13 15:17:40 +08:00
DismissedLight
fad5f8ada3 Merge pull request #1473 from DGP-Studio/develop 2024-03-13 14:15:42 +08:00
Lightczx
238c2036f6 bump version 2024-03-13 14:03:49 +08:00
DismissedLight
e48f740e46 Merge pull request #1472 from DGP-Studio/l10n_develop 2024-03-13 13:57:16 +08:00
Masterain
8574a3d825 New translations sh.resx (English) 2024-03-12 22:56:21 -07:00
DismissedLight
08f27e8ee7 Merge pull request #1463 from DGP-Studio/l10n_develop 2024-03-13 13:55:23 +08:00
Lightczx
c8b3779d97 compat UIGF v3.0 2024-03-13 11:43:39 +08:00
Lightczx
54cbd9dfb9 fix web cache query provider 2024-03-13 10:45:34 +08:00
DismissedLight
65517abcb4 Merge pull request #1469 from DGP-Studio/fix/titlebar_darkmode 2024-03-13 09:04:26 +08:00
qhy040404
87c3043fd9 fix wrong titlebar color when manually manually setting the color theme 2024-03-12 22:34:32 +08:00
DismissedLight
762bc14b88 adjust ui for city banner 2024-03-12 21:35:06 +08:00
DismissedLight
51dfc7020f Merge pull request #1466 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-ba3ce57765 2024-03-12 09:37:41 +08:00
dependabot[bot]
47dda1bebc Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [MSTest.TestAdapter](https://github.com/microsoft/testfx).


Updates `MSTest.TestAdapter` from 3.2.1 to 3.2.2
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.1...v3.2.2)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 07:12:02 +00:00
Lightczx
1a300e8b9c fix predownload latest path and name 2024-03-11 11:47:43 +08:00
Lightczx
16e8a17614 disable launchoptions by default 2024-03-11 11:24:41 +08:00
Lightczx
a5c75f9465 revert BGI process waitforinputidle 2024-03-11 11:16:23 +08:00
Lightczx
0cb59808a1 refactor unlocking fps 2024-03-11 11:01:57 +08:00
Masterain
80439ed673 New translations sh.resx (Chinese Traditional) 2024-03-10 18:37:38 -07:00
Lightczx
d632002e4b Sync Scighost/Starward#692 2024-03-11 09:33:21 +08:00
DismissedLight
cf6a972d55 Update FeedbackPage.xaml 2024-03-10 22:39:11 +08:00
DismissedLight
e1a976f02d fix theme switch dropping shadow 2024-03-10 22:28:03 +08:00
DismissedLight
dbedc2a00d fix equal panel spacing 2024-03-10 22:23:10 +08:00
DismissedLight
03312f1d52 Merge pull request #1464 from DGP-Studio/fix/horizontal_equal_panel 2024-03-10 21:37:03 +08:00
qhy040404
47f29de9c6 fix measure 2024-03-10 21:30:15 +08:00
Masterain
708da5769a New translations sh.resx (Indonesian) 2024-03-10 01:31:59 -08:00
Masterain
8bce2d3b28 New translations sh.resx (English) 2024-03-10 01:31:58 -08:00
Masterain
5252e7b451 New translations sh.resx (Chinese Traditional) 2024-03-10 01:31:56 -08:00
Masterain
2f5d884425 New translations sh.resx (Russian) 2024-03-10 01:31:55 -08:00
Masterain
5aea3695b9 New translations sh.resx (Portuguese) 2024-03-10 01:31:54 -08:00
Masterain
462e570bc1 New translations sh.resx (Korean) 2024-03-10 01:31:53 -08:00
Masterain
511d113c65 New translations sh.resx (Japanese) 2024-03-10 01:31:52 -08:00
Lightczx
2a5e7e0136 temp progress 2024-03-10 17:30:57 +08:00
Lightczx
177e7a6233 fix #1459 2024-03-10 14:29:57 +08:00
DismissedLight
bd09b303ab Merge pull request #1456 from DGP-Studio/fix/darkmode
fix #1434
2024-03-10 14:13:10 +08:00
qhy040404
5538b74939 remove WindowOptions.UseSystemBackdrop 2024-03-10 12:16:30 +08:00
qhy040404
fda289421b code style 2024-03-10 12:16:30 +08:00
qhy040404
c0abe7f7c5 fix #1434 2024-03-10 12:16:30 +08:00
DismissedLight
e95732d448 Merge pull request #1454 from DGP-Studio/fix/navigate_crash 2024-03-10 12:10:13 +08:00
DismissedLight
9e52a87d11 Merge pull request #1457 from DGP-Studio/fix/composition_image_delay 2024-03-10 12:03:29 +08:00
qhy040404
66bce570cc Update build.cake 2024-03-10 10:46:46 +08:00
qhy040404
a5091134b0 fix typo 2024-03-10 10:46:46 +08:00
qhy040404
59c9845ad0 fix navigation crash 2024-03-10 10:46:46 +08:00
qhy040404
2db829e1e2 add delay to fix blink again 2024-03-10 10:46:37 +08:00
DismissedLight
1a64328270 Update GachaTypeComparer.cs 2024-03-09 16:48:37 +08:00
DismissedLight
7a572631e9 Merge pull request #1415 from DGP-Studio/develop 2024-02-21 20:35:56 +08:00
113 changed files with 2643 additions and 1864 deletions

View File

@@ -69,6 +69,15 @@ else if (AppVeyor.IsRunningOnAppVeyor)
})[..^2];
Information($"Version: {version}");
}
else // Local
{
repoDir = System.Environment.CurrentDirectory;
outputPath = System.IO.Path.Combine(repoDir, "src", "output");
version = System.DateTime.Now.ToString("yyyy.M.d.") + ((int)((System.DateTime.Now - System.DateTime.Today).TotalSeconds / 86400 * 65535)).ToString();
Information($"Version: {version}");
}
Task("Build")
.IsDependentOn("Build binary package")
@@ -112,6 +121,17 @@ Task("Generate AppxManifest")
Information("Using Release configuration");
content = System.Text.RegularExpressions.Regex.Replace(content, " Publisher=\"([^\"]*)\"", " Publisher=\"CN=SignPath Foundation, O=SignPath Foundation, L=Lewes, S=Delaware, C=US\"");
}
else
{
Information("Using Local configuration.");
content = content
.Replace("Snap Hutao", "Snap Hutao Local")
.Replace("胡桃", "胡桃 Local")
.Replace("DGP Studio", "DGP Studio CI");
content = System.Text.RegularExpressions.Regex.Replace(content, " Name=\"([^\"]*)\"", " Name=\"E8B6E2B3-D2A0-4435-A81D-2A16AAF405C7\"");
content = System.Text.RegularExpressions.Regex.Replace(content, " Publisher=\"([^\"]*)\"", " Publisher=\"E=admin@dgp-studio.cn, CN=DGP Studio CI, OU=CI, O=DGP-Studio, L=San Jose, S=CA, C=US\"");
content = System.Text.RegularExpressions.Regex.Replace(content, " Version=\"([0-9\\.]+)\"", $" Version=\"{version}\"");
}
System.IO.File.WriteAllText(manifest, content);
@@ -173,6 +193,10 @@ Task("Build MSIX")
{
arguments = "pack /d " + binPath + " /p " + System.IO.Path.Combine(outputPath, $"Snap.Hutao-{version}.msix");
}
else
{
arguments = "pack /d " + binPath + " /p " + System.IO.Path.Combine(outputPath, $"Snap.Hutao.Local-{version}.msix");
}
var p = StartProcess(
"makeappx.exe",
new ProcessSettings

View File

@@ -13,9 +13,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -29,6 +29,7 @@
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>
<ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/>
<ResourceDictionary Source="ms-appx:///View/Card/Primitive/CardProgressBar.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style

View File

@@ -7,8 +7,11 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.LifeCycle.InterProcess;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Core.Shell;
using System.Diagnostics;
using System.Text;
using static Snap.Hutao.Core.Logging.ConsoleVirtualTerminalSequences;
namespace Snap.Hutao;
@@ -21,17 +24,17 @@ namespace Snap.Hutao;
[SuppressMessage("", "SH001")]
public sealed partial class App : Application
{
private const string ConsoleBanner = """
private const string ConsoleBanner = $"""
----------------------------------------------------------------
_____ _ _ _
/ ____| | | | | | |
| (___ _ __ __ _ _ __ | |__| | _ _ | |_ __ _ ___
\___ \ | '_ \ / _` || '_ \ | __ || | | || __|/ _` | / _ \
_____ _ _ _
/ ____| | | | | | |
| (___ _ __ __ _ _ __ | |__| | _ _ | |_ __ _ ___
\___ \ | '_ \ / _` || '_ \ | __ || | | || __|/ _` | / _ \
____) || | | || (_| || |_) |_ | | | || |_| || |_| (_| || (_) |
|_____/ |_| |_| \__,_|| .__/(_)|_| |_| \__,_| \__|\__,_| \___/
| |
|_|
|_____/ |_| |_| \__,_|| .__/(_)|_| |_| \__,_| \__|\__,_| \___/
| |
|_|
Snap.Hutao is a open source software developed by DGP Studio.
Copyright (C) 2022 - 2024 DGP Studio, All Rights Reserved.
----------------------------------------------------------------
@@ -69,7 +72,7 @@ public sealed partial class App : Application
return;
}
logger.LogInformation(ConsoleBanner);
logger.LogColorizedInformation((ConsoleBanner, ConsoleColor.DarkYellow));
LogDiagnosticInformation();
// manually invoke
@@ -89,8 +92,8 @@ public sealed partial class App : Application
{
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
logger.LogInformation("FamilyName: {name}", runtimeOptions.FamilyName);
logger.LogInformation("Version: {version}", runtimeOptions.Version);
logger.LogInformation("LocalCache: {folder}", runtimeOptions.LocalCache);
logger.LogColorizedInformation(("FamilyName: {Name}", ConsoleColor.Blue), (runtimeOptions.FamilyName, ConsoleColor.Cyan));
logger.LogColorizedInformation(("Version: {Version}", ConsoleColor.Blue), (runtimeOptions.Version, ConsoleColor.Cyan));
logger.LogColorizedInformation(("LocalCache: {Path}", ConsoleColor.Blue), (runtimeOptions.LocalCache, ConsoleColor.Cyan));
}
}

View File

@@ -1,9 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Snap.Hutao.Control.Extension;
using System.Collections;
namespace Snap.Hutao.Control.AutoSuggestBox;
@@ -18,23 +22,44 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
TextChanged += OnFilterSuggestionRequested;
QuerySubmitted += OnQuerySubmitted;
TokenItemAdding += OnTokenItemAdding;
TokenItemAdded += OnTokenItemModified;
TokenItemRemoved += OnTokenItemModified;
TokenItemAdded += OnTokenItemCollectionChanged;
TokenItemRemoved += OnTokenItemCollectionChanged;
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.FindDescendant("SuggestionsPopup") is Popup { Child: Border { Child: ListView listView } border })
{
IAppResourceProvider appResourceProvider = this.ServiceProvider().GetRequiredService<IAppResourceProvider>();
listView.Background = null;
listView.Margin = appResourceProvider.GetResource<Thickness>("AutoSuggestListPadding");
border.Background = appResourceProvider.GetResource<Microsoft.UI.Xaml.Media.Brush>("AutoSuggestBoxSuggestionsListBackground");
CornerRadius overlayCornerRadius = appResourceProvider.GetResource<CornerRadius>("OverlayCornerRadius");
CornerRadiusFilterConverter cornerRadiusFilterConverter = new() { Filter = CornerRadiusFilterKind.Bottom };
border.CornerRadius = (CornerRadius)cornerRadiusFilterConverter.Convert(overlayCornerRadius, typeof(CornerRadius), default, default);
}
}
private void OnFilterSuggestionRequested(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (string.IsNullOrWhiteSpace(Text))
{
return;
sender.ItemsSource = AvailableTokens
.OrderBy(kvp => kvp.Value.Kind)
.Select(kvp => kvp.Value);
}
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
sender.ItemsSource = AvailableTokens.Values.Where(q => q.Value.Contains(Text, StringComparison.OrdinalIgnoreCase));
// TODO: CornerRadius
// Popup? popup = this.FindDescendant("SuggestionsPopup") as Popup;
sender.ItemsSource = AvailableTokens
.Where(kvp => kvp.Value.Value.Contains(Text, StringComparison.OrdinalIgnoreCase))
.OrderBy(kvp => kvp.Value.Kind)
.ThenBy(kvp => kvp.Value.Order)
.Select(kvp => kvp.Value)
.DefaultIfEmpty(SearchToken.NotFound);
}
}
@@ -45,7 +70,7 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
return;
}
CommandExtension.TryExecute(FilterCommand, FilterCommandParameter);
CommandInvocation.TryExecute(FilterCommand, FilterCommandParameter);
}
private void OnTokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args)
@@ -55,11 +80,23 @@ internal sealed partial class AutoSuggestTokenBox : TokenizingTextBox
return;
}
args.Item = AvailableTokens.GetValueOrDefault(args.TokenText) ?? new SearchToken(SearchTokenKind.None, args.TokenText);
if (AvailableTokens.GetValueOrDefault(args.TokenText) is { } token)
{
args.Item = token;
}
else
{
args.Cancel = true;
}
}
private void OnTokenItemModified(TokenizingTextBox sender, object args)
private void OnTokenItemCollectionChanged(TokenizingTextBox sender, object args)
{
CommandExtension.TryExecute(FilterCommand, FilterCommandParameter);
if (args is SearchToken { Kind: SearchTokenKind.None } token)
{
((IList)sender.ItemsSource).Remove(token);
}
FilterCommand.TryExecute(FilterCommandParameter);
}
}

View File

@@ -7,13 +7,16 @@ namespace Snap.Hutao.Control.AutoSuggestBox;
internal sealed class SearchToken
{
public SearchToken(SearchTokenKind kind, string value, Uri? iconUri = null, Uri? sideIconUri = null, Color? quality = null)
public static readonly SearchToken NotFound = new(SearchTokenKind.None, SH.ControlAutoSuggestBoxNotFoundValue, 0);
public SearchToken(SearchTokenKind kind, string value, int order, Uri? iconUri = null, Uri? sideIconUri = null, Color? quality = null)
{
Value = value;
Kind = kind;
IconUri = iconUri;
SideIconUri = sideIconUri;
Quality = quality;
Order = order;
}
public SearchTokenKind Kind { get; }
@@ -26,6 +29,8 @@ internal sealed class SearchToken
public Color? Quality { get; }
public int Order { get; }
public override string ToString()
{
return Value;

View File

@@ -6,12 +6,12 @@ namespace Snap.Hutao.Control.AutoSuggestBox;
internal enum SearchTokenKind
{
None,
AssociationType,
Avatar,
BodyType,
ElementName,
FightProperty,
ItemQuality,
Weapon,
WeaponType,
FightProperty,
ElementName,
AssociationType,
BodyType,
Avatar,
Weapon,
}

View File

@@ -33,6 +33,7 @@ internal sealed partial class PeriodicInvokeCommandOrOnActualThemeChangedBehavio
protected override bool Uninitialize()
{
periodicTimerCancellationTokenSource.Cancel();
AssociatedObject.ActualThemeChanged -= OnActualThemeChanged;
return true;
}

View File

@@ -13,6 +13,4 @@ namespace Snap.Hutao.Control;
/// </summary>
[HighQuality]
[DependencyProperty("DataContext", typeof(object))]
internal sealed partial class BindingProxy : DependencyObject
{
}
internal sealed partial class BindingProxy : DependencyObject;

View File

@@ -30,7 +30,7 @@ internal sealed class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, IN
private WeakEventListener<AdvancedCollectionView<T>, object?, NotifyCollectionChangedEventArgs>? sourceWeakEventListener;
public AdvancedCollectionView()
: this(new List<T>(0))
: this([])
{
}

View File

@@ -6,6 +6,7 @@ using Windows.Foundation.Collections;
namespace Snap.Hutao.Control.Collection.Alternating;
[Obsolete("Use SettingsCard instead")]
[DependencyProperty("ItemAlternateBackground", typeof(Microsoft.UI.Xaml.Media.Brush))]
internal sealed partial class AlternatingItemsControl : ItemsControl
{

View File

@@ -3,6 +3,7 @@
namespace Snap.Hutao.Control.Collection.Alternating;
[Obsolete("Use SettingsCard instead")]
internal interface IAlternatingItem
{
public Microsoft.UI.Xaml.Media.Brush? Background { get; set; }

View File

@@ -3,7 +3,7 @@
namespace Snap.Hutao.Control.Extension;
internal static class CommandExtension
internal static class CommandInvocation
{
public static bool TryExecute(this ICommand? command, object? parameter = null)
{

View File

@@ -2,11 +2,13 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Control.Extension;
internal static class DependencyObjectExtension
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IServiceProvider ServiceProvider(this DependencyObject obj)
{
return Ioc.Default;

View File

@@ -3,7 +3,9 @@
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.ExceptionService;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Control.Image;
@@ -19,6 +21,9 @@ internal sealed class CachedImage : Implementation.ImageEx
/// </summary>
public CachedImage()
{
DefaultStyleKey = typeof(CachedImage);
DefaultStyleResourceUri = "ms-appx:///Control/Image/CachedImage.xaml".ToUri();
IsCacheEnabled = true;
EnableLazyLoading = false;
}
@@ -26,12 +31,11 @@ internal sealed class CachedImage : Implementation.ImageEx
/// <inheritdoc/>
protected override async Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
{
// We can only use Ioc to retrieve IImageCache, no IServiceProvider is available.
IImageCache imageCache = Ioc.Default.GetRequiredService<IImageCache>();
IImageCache imageCache = this.ServiceProvider().GetRequiredService<IImageCache>();
try
{
Verify.Operation(!string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri);
HutaoException.ThrowIf(string.IsNullOrEmpty(imageUri.Host), HutaoExceptionKind.ImageCacheInvalidUri, SH.ControlImageCachedImageInvalidResourceUri);
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread.
token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled.
return new BitmapImage(file.ToUri()); // BitmapImage initialize with a uri will increase image quality and loading speed.

View File

@@ -7,21 +7,14 @@ using Windows.Media.Casting;
namespace Snap.Hutao.Control.Image.Implementation;
internal class ImageEx : ImageExBase
[DependencyProperty("NineGrid", typeof(Thickness))]
internal partial class ImageEx : ImageExBase
{
private static readonly DependencyProperty NineGridProperty = DependencyProperty.Register(nameof(NineGrid), typeof(Thickness), typeof(ImageEx), new PropertyMetadata(default(Thickness)));
public ImageEx()
: base()
{
}
public Thickness NineGrid
{
get => (Thickness)GetValue(NineGridProperty);
set => SetValue(NineGridProperty, value);
}
public override CompositionBrush GetAlphaMask()
{
if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)

View File

@@ -6,6 +6,7 @@ using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Win32;
using System.IO;
using Windows.Foundation;
@@ -158,7 +159,6 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
if (value)
{
control.LayoutUpdated += control.OnImageExBaseLayoutUpdated;
control.InvalidateLazyLoading();
}
else
@@ -169,7 +169,7 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
private static void LazyLoadingThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageExBase control && control.EnableLazyLoading)
if (d is ImageExBase { EnableLazyLoading: true } control)
{
control.InvalidateLazyLoading();
}
@@ -229,9 +229,6 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
private void AttachPlaceholderSource(ImageSource? source)
{
// Setting the source at this point should call ImageExOpened/VisualStateManager.GoToState
// as we register to both the ImageOpened/ImageFailed events of the underlying control.
// We only need to call those methods if we fail in other cases before we get here.
if (PlaceholderImage is Microsoft.UI.Xaml.Controls.Image image)
{
image.Source = source;
@@ -240,6 +237,15 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
{
brush.ImageSource = source;
}
if (source is null)
{
VisualStateManager.GoToState(this, UnloadedState, true);
}
else if (source is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 })
{
VisualStateManager.GoToState(this, LoadedState, true);
}
}
private async void SetSource(object? source)
@@ -311,8 +317,7 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
}
tokenSource?.Cancel();
tokenSource = new CancellationTokenSource();
tokenSource = new();
AttachPlaceholderSource(null);
@@ -443,9 +448,10 @@ internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control
return;
}
Rect controlRect = TransformToVisual(hostElement)
.TransformBounds(new Rect(0, 0, ActualWidth, ActualHeight));
Rect controlRect = TransformToVisual(hostElement).TransformBounds(StructMarshal.Rect(ActualSize));
double lazyLoadingThreshold = LazyLoadingThreshold;
// Left/Top 1 Threshold, Right/Bottom 2 Threshold
Rect hostRect = new(
0 - lazyLoadingThreshold,
0 - lazyLoadingThreshold,

View File

@@ -1,151 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using System.Numerics;
using Windows.Foundation;
namespace Snap.Hutao.Control.Layout;
internal sealed class DefaultItemCollectionTransitionProvider : ItemCollectionTransitionProvider
{
private const double DefaultAnimationDurationInMs = 300.0;
static DefaultItemCollectionTransitionProvider()
{
AnimationSlowdownFactor = 1.0;
}
public static double AnimationSlowdownFactor { get; set; }
protected override bool ShouldAnimateCore(ItemCollectionTransition transition)
{
return true;
}
protected override void StartTransitions(IList<ItemCollectionTransition> transitions)
{
List<ItemCollectionTransition> addTransitions = [];
List<ItemCollectionTransition> removeTransitions = [];
List<ItemCollectionTransition> moveTransitions = [];
foreach (ItemCollectionTransition transition in addTransitions)
{
switch (transition.Operation)
{
case ItemCollectionTransitionOperation.Add:
addTransitions.Add(transition);
break;
case ItemCollectionTransitionOperation.Remove:
removeTransitions.Add(transition);
break;
case ItemCollectionTransitionOperation.Move:
moveTransitions.Add(transition);
break;
}
}
StartAddTransitions(addTransitions, removeTransitions.Count > 0, moveTransitions.Count > 0);
StartRemoveTransitions(removeTransitions);
StartMoveTransitions(moveTransitions, removeTransitions.Count > 0);
}
private static void StartAddTransitions(IList<ItemCollectionTransition> transitions, bool hasRemoveTransitions, bool hasMoveTransitions)
{
foreach (ItemCollectionTransition transition in transitions)
{
ItemCollectionTransitionProgress progress = transition.Start();
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
Compositor compositor = visual.Compositor;
ScalarKeyFrameAnimation fadeInAnimation = compositor.CreateScalarKeyFrameAnimation();
fadeInAnimation.InsertKeyFrame(0.0f, 0.0f);
if (hasMoveTransitions && hasRemoveTransitions)
{
fadeInAnimation.InsertKeyFrame(0.66f, 0.0f);
}
else if (hasMoveTransitions || hasRemoveTransitions)
{
fadeInAnimation.InsertKeyFrame(0.5f, 0.0f);
}
fadeInAnimation.InsertKeyFrame(1.0f, 1.0f);
fadeInAnimation.Duration = TimeSpan.FromMilliseconds(
DefaultAnimationDurationInMs * ((hasRemoveTransitions ? 1 : 0) + (hasMoveTransitions ? 1 : 0) + 1) * AnimationSlowdownFactor);
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
visual.StartAnimation("Opacity", fadeInAnimation);
batch.End();
batch.Completed += (_, _) => progress.Complete();
}
}
private static void StartRemoveTransitions(IList<ItemCollectionTransition> transitions)
{
foreach (ItemCollectionTransition transition in transitions)
{
ItemCollectionTransitionProgress progress = transition.Start();
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
Compositor compositor = visual.Compositor;
ScalarKeyFrameAnimation fadeOutAnimation = compositor.CreateScalarKeyFrameAnimation();
fadeOutAnimation.InsertExpressionKeyFrame(0.0f, "this.CurrentValue");
fadeOutAnimation.InsertKeyFrame(1.0f, 0.0f);
fadeOutAnimation.Duration = TimeSpan.FromMilliseconds(DefaultAnimationDurationInMs * AnimationSlowdownFactor);
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
visual.StartAnimation(nameof(Visual.Opacity), fadeOutAnimation);
batch.End();
batch.Completed += (_, _) =>
{
visual.Opacity = 1.0f;
progress.Complete();
};
}
}
private static void StartMoveTransitions(IList<ItemCollectionTransition> transitions, bool hasRemoveAnimations)
{
foreach (ItemCollectionTransition transition in transitions)
{
ItemCollectionTransitionProgress progress = transition.Start();
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
Compositor compositor = visual.Compositor;
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
// Animate offset.
if (transition.OldBounds.X != transition.NewBounds.X ||
transition.OldBounds.Y != transition.NewBounds.Y)
{
AnimateOffset(visual, compositor, transition.OldBounds, transition.NewBounds, hasRemoveAnimations);
}
batch.End();
batch.Completed += (_, _) => progress.Complete();
}
}
private static void AnimateOffset(Visual visual, Compositor compositor, Rect oldBounds, Rect newBounds, bool hasRemoveAnimations)
{
Vector2KeyFrameAnimation offsetAnimation = compositor.CreateVector2KeyFrameAnimation();
offsetAnimation.SetVector2Parameter("delta", new Vector2(
(float)(oldBounds.X - newBounds.X),
(float)(oldBounds.Y - newBounds.Y)));
offsetAnimation.SetVector2Parameter("final", default);
offsetAnimation.InsertExpressionKeyFrame(0.0f, "this.CurrentValue + delta");
if (hasRemoveAnimations)
{
offsetAnimation.InsertExpressionKeyFrame(0.5f, "delta");
}
offsetAnimation.InsertExpressionKeyFrame(1.0f, "final");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(
DefaultAnimationDurationInMs * ((hasRemoveAnimations ? 1 : 0) + 1) * AnimationSlowdownFactor);
visual.StartAnimation("TransformMatrix._41_42", offsetAnimation);
}
}

View File

@@ -4,6 +4,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Foundation;
@@ -18,14 +19,12 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
protected override void InitializeForContextCore(VirtualizingLayoutContext context)
{
context.LayoutState = new UniformStaggeredLayoutState(context);
base.InitializeForContextCore(context);
}
/// <inheritdoc/>
protected override void UninitializeForContextCore(VirtualizingLayoutContext context)
{
context.LayoutState = null;
base.UninitializeForContextCore(context);
}
/// <inheritdoc/>
@@ -82,16 +81,10 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
(int numberOfColumns, double columnWidth) = GetNumberOfColumnsAndWidth(availableWidth, MinItemWidth, MinColumnSpacing);
if (columnWidth != state.ColumnWidth)
{
// The items will need to be remeasured
state.Clear();
}
state.ColumnWidth = columnWidth;
// adjust for column spacing on all columns expect the first
double totalWidth = state.ColumnWidth + ((numberOfColumns - 1) * (state.ColumnWidth + MinColumnSpacing));
double totalWidth = ((state.ColumnWidth + MinColumnSpacing) * numberOfColumns) - MinColumnSpacing;
if (totalWidth > availableWidth)
{
numberOfColumns--;
@@ -103,7 +96,6 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
if (numberOfColumns != state.NumberOfColumns)
{
// The items will not need to be remeasured, but they will need to go into new columns
state.ClearColumns();
}
@@ -170,7 +162,7 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
item.Element.Measure(new Size(state.ColumnWidth, availableHeight));
if (item.Height != item.Element.DesiredSize.Height)
{
// this item changed size; we need to recalculate layout for everything after this
// this item changed size; we need to recalculate layout for everything after this item
state.RemoveFromIndex(i + 1);
item.Height = item.Element.DesiredSize.Height;
columnHeights[columnIndex] = item.Top + item.Height;
@@ -201,16 +193,16 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
// Cycle through each column and arrange the items that are within the realization bounds
for (int columnIndex = 0; columnIndex < state.NumberOfColumns; columnIndex++)
{
UniformStaggeredColumnLayout layout = state.GetColumnLayout(columnIndex);
foreach (ref readonly UniformStaggeredItem item in CollectionsMarshal.AsSpan(layout))
foreach (ref readonly UniformStaggeredItem item in CollectionsMarshal.AsSpan(state.GetColumnLayout(columnIndex)))
{
double bottom = item.Top + item.Height;
if (bottom < context.RealizationRect.Top)
{
// element is above the realization bounds
// Element is above the realization bounds
continue;
}
// Partial or fully in the view
if (item.Top <= context.RealizationRect.Bottom)
{
double itemHorizontalOffset = (state.ColumnWidth * columnIndex) + (MinColumnSpacing * columnIndex);
@@ -229,21 +221,22 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
return finalSize;
}
private static (int NumberOfColumns, double ColumnWidth) GetNumberOfColumnsAndWidth(double availableWidth, double minItemWidth, double minColumnSpacing)
private static (int NumberOfColumns, double ColumnWidth) GetNumberOfColumnsAndWidth(double availableWidth, double minItemWidth, double columnSpacing)
{
// test if the width can fit in 2 items
if ((2 * minItemWidth) + minColumnSpacing > availableWidth)
if ((2 * minItemWidth) + columnSpacing > availableWidth)
{
return (1, availableWidth);
}
int columnCount = Math.Max(1, (int)((availableWidth + minColumnSpacing) / (minItemWidth + minColumnSpacing)));
double columnWidthAddSpacing = (availableWidth + minColumnSpacing) / columnCount;
return (columnCount, columnWidthAddSpacing - minColumnSpacing);
int columnCount = Math.Max(1, (int)((availableWidth + columnSpacing) / (minItemWidth + columnSpacing)));
double columnWidthWithSpacing = (availableWidth + columnSpacing) / columnCount;
return (columnCount, columnWidthWithSpacing - columnSpacing);
}
private static int GetLowestColumnIndex(in ReadOnlySpan<double> columnHeights)
{
// We want to find the leftest column with the lowest height
int columnIndex = 0;
double height = columnHeights[0];
for (int j = 1; j < columnHeights.Length; j++)
@@ -260,13 +253,11 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
private static void OnMinItemWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UniformStaggeredLayout panel = (UniformStaggeredLayout)d;
panel.InvalidateMeasure();
((UniformStaggeredLayout)d).InvalidateMeasure();
}
private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UniformStaggeredLayout panel = (UniformStaggeredLayout)d;
panel.InvalidateMeasure();
((UniformStaggeredLayout)d).InvalidateMeasure();
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Runtime.InteropServices;
@@ -67,46 +66,6 @@ internal sealed class UniformStaggeredLayoutState
return columnLayout[columnIndex];
}
/// <summary>
/// Clear everything that has been calculated.
/// </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();
}
/// <summary>
/// Clear the layout columns so they will be recalculated.
/// </summary>
internal void ClearColumns()
{
columnLayout.Clear();
}
/// <summary>
/// Gets the estimated height of the layout.
/// </summary>
/// <returns>The estimated height of the layout.</returns>
/// <remarks>
/// If all of the items have been calculated then the actual height will be returned.
/// If all of the items have not been calculated then an estimated height will be calculated based on the average height of the items.
/// </remarks>
internal double GetHeight()
{
double desiredHeight = columnLayout.Values.Max(c => c.Height);
@@ -139,10 +98,37 @@ internal sealed class UniformStaggeredLayoutState
return desiredHeight;
}
internal void Clear()
{
RecycleElements();
ClearColumns();
ClearItems();
}
internal void ClearColumns()
{
columnLayout.Clear();
}
internal void ClearItems()
{
items.Clear();
}
internal void RecycleElements()
{
if (context.ItemCount > 0)
{
for (int i = 0; i < items.Count; i++)
{
RecycleElementAt(i);
}
}
}
internal void RecycleElementAt(int index)
{
UIElement element = context.GetOrCreateElementAt(index);
context.RecycleElement(element);
context.RecycleElement(context.GetOrCreateElementAt(index));
}
internal void RemoveFromIndex(int index)
@@ -175,7 +161,7 @@ internal sealed class UniformStaggeredLayoutState
{
for (int i = startIndex; i <= endIndex; i++)
{
if (i > items.Count)
if (i >= items.Count)
{
break;
}
@@ -184,7 +170,7 @@ internal sealed class UniformStaggeredLayoutState
item.Height = 0;
item.Top = 0;
// We must recycle all elements to ensure that it gets the correct context
// We must recycle all removed elements to ensure that it gets the correct context
RecycleElementAt(i);
}

View File

@@ -17,7 +17,7 @@ internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
public Loading()
{
DefaultStyleKey = typeof(Loading);
DefaultStyleResourceUri = new("ms-appx:///Control/Loading.xaml");
DefaultStyleResourceUri = "ms-appx:///Control/Loading.xaml".ToUri();
}
public bool IsLoading

View File

@@ -12,7 +12,6 @@ internal sealed class UInt32Extension : MarkupExtension
protected override object ProvideValue()
{
_ = uint.TryParse(Value, out uint result);
return result;
return XamlBindingHelper.ConvertValue(typeof(uint), Value);
}
}

View File

@@ -39,24 +39,6 @@ internal static class SoftwareBitmapExtension
}
}
public static unsafe double Luminance(this SoftwareBitmap softwareBitmap)
{
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
{
using (IMemoryBufferReference reference = buffer.CreateReference())
{
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<Bgra32> bytes);
double sum = 0;
foreach (ref readonly Bgra32 pixel in bytes)
{
sum += pixel.Luminance;
}
return sum / bytes.Length;
}
}
}
public static unsafe Bgra32 GetAccentColor(this SoftwareBitmap softwareBitmap)
{
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))

View File

@@ -82,8 +82,8 @@ internal partial class EqualPanel : Microsoft.UI.Xaml.Controls.Panel
(d as EqualPanel)?.InvalidateMeasure();
}
private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp)
private static void OnHorizontalAlignmentChanged(DependencyObject d, DependencyProperty dp)
{
InvalidateMeasure();
(d as EqualPanel)?.InvalidateMeasure();
}
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Windows.Foundation;
namespace Snap.Hutao.Control.Panel;
[DependencyProperty("MinItemWidth", typeof(double))]
[DependencyProperty("Spacing", typeof(double))]
internal partial class HorizontalEqualPanel : Microsoft.UI.Xaml.Controls.Panel
{
public HorizontalEqualPanel()
{
Loaded += OnLoaded;
SizeChanged += OnSizeChanged;
}
protected override Size MeasureOverride(Size availableSize)
{
foreach (UIElement child in Children)
{
// ScrollViewer will always return an Infinity Size, we should use ActualWidth for this situation.
double availableWidth = double.IsInfinity(availableSize.Width) ? ActualWidth : availableSize.Width;
double childAvailableWidth = (availableWidth + Spacing) / Children.Count;
double childMaxAvailableWidth = Math.Max(MinItemWidth, childAvailableWidth);
child.Measure(new(childMaxAvailableWidth - Spacing, ActualHeight));
}
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
int itemCount = Children.Count;
double availableItemWidth = (finalSize.Width - (Spacing * (itemCount - 1))) / itemCount;
double actualItemWidth = Math.Max(MinItemWidth, availableItemWidth);
double offset = 0;
foreach (UIElement child in Children)
{
child.Arrange(new Rect(offset, 0, actualItemWidth, finalSize.Height));
offset += actualItemWidth + Spacing;
}
return finalSize;
}
private static void OnLoaded(object sender, RoutedEventArgs e)
{
HorizontalEqualPanel panel = (HorizontalEqualPanel)sender;
panel.MinWidth = (panel.MinItemWidth * panel.Children.Count) + (panel.Spacing * (panel.Children.Count - 1));
}
private static void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
((HorizontalEqualPanel)sender).InvalidateMeasure();
}
}

View File

@@ -1,21 +1,55 @@
// 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 Microsoft.UI.Xaml.Controls.Primitives;
using Windows.Foundation;
namespace Snap.Hutao.Control.Panel;
[DependencyProperty("MinItemWidth", typeof(double))]
internal sealed partial class UniformPanel : UniformGrid
[DependencyProperty("ColumnSpacing", typeof(double))]
[DependencyProperty("RowSpacing", typeof(double))]
internal sealed partial class UniformPanel : Microsoft.UI.Xaml.Controls.Panel
{
public UniformPanel()
private int columns;
protected override Size MeasureOverride(Size availableSize)
{
Columns = 1;
SizeChanged += OnSizeChanged;
columns = (int)((availableSize.Width + ColumnSpacing) / (MinItemWidth + ColumnSpacing));
double availableItemWidth = ((availableSize.Width + ColumnSpacing) / columns) - ColumnSpacing;
double maxDesiredHeight = 0;
foreach (UIElement child in Children)
{
child.Measure(new Size(availableItemWidth, availableSize.Height));
maxDesiredHeight = Math.Max(maxDesiredHeight, child.DesiredSize.Height);
}
int desiredRows = (int)Math.Ceiling(Children.Count / (double)columns);
double desiredHeight = ((maxDesiredHeight + RowSpacing) * desiredRows) - RowSpacing;
return new Size(availableSize.Width, desiredHeight);
}
private void OnSizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e)
protected override Size ArrangeOverride(Size finalSize)
{
Columns = (int)((e.NewSize.Width + ColumnSpacing) / (MinItemWidth + ColumnSpacing));
double itemWidth = ((finalSize.Width + ColumnSpacing) / columns) - ColumnSpacing;
for (int index = 0; index < Children.Count; index++)
{
UIElement child = Children[index];
int row = index / columns;
int column = index % columns;
double x = column * (itemWidth + ColumnSpacing);
double y = row * (child.DesiredSize.Height + RowSpacing);
child.Arrange(new Rect(x, y, itemWidth, child.DesiredSize.Height));
}
return finalSize;
}
}

View File

@@ -72,7 +72,11 @@ internal class ScopedPage : Page
DisposeViewModel();
}
DataContext = null;
if (this.IsDisposed())
{
return;
}
Unloaded -= unloadEventHandler;
}

View File

@@ -45,15 +45,7 @@ internal sealed partial class DescriptionTextBlock : ContentControl
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
try
{
UpdateDescription(textBlock, MiHoYoSyntaxTree.Parse(SpecialNameHandler.Handle((string)e.NewValue)));
}
catch (Exception ex)
{
_ = ex;
}
UpdateDescription(textBlock, MiHoYoSyntaxTree.Parse(SpecialNameHandler.Handle((string)e.NewValue)));
}
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

View File

@@ -14,6 +14,7 @@ using Windows.UI;
namespace Snap.Hutao.Control.Text;
// TODO: change the parsing to syntax tree
[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
internal sealed partial class HtmlDescriptionTextBlock : ContentControl

View File

@@ -4,21 +4,19 @@
xmlns:cwm="using:CommunityToolkit.WinUI.Media">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<cwm:AttachedCardShadow
x:Key="CompatCardShadow"
BlurRadius="8"
Opacity="0.14"
Offset="0,4,0"/>
<x:Double x:Key="CompatShadowThemeOpacity">0.14</x:Double>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<cwm:AttachedCardShadow
x:Key="CompatCardShadow"
BlurRadius="8"
Opacity="0.28"
Offset="0,4,0"/>
<x:Double x:Key="CompatShadowThemeOpacity">0.28</x:Double>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<cwm:AttachedCardShadow
x:Key="CompatCardShadow"
BlurRadius="8"
Opacity="{ThemeResource CompatShadowThemeOpacity}"
Offset="0,4,0"/>
<Style x:Key="BorderCardStyle" TargetType="Border">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>

View File

@@ -8,6 +8,9 @@
<ItemsPanelTemplate x:Key="WrapPanelSpacing0Template">
<cwcont:WrapPanel/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="WrapPanelSpacing2Template">
<cwcont:WrapPanel HorizontalSpacing="2" VerticalSpacing="2"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="WrapPanelSpacing4Template">
<cwcont:WrapPanel HorizontalSpacing="4" VerticalSpacing="4"/>
</ItemsPanelTemplate>

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32;
using Windows.UI;
namespace Snap.Hutao.Control.Theme;
internal static class SystemColors
{
public static Color BaseLowColor(bool isDarkMode)
{
return isDarkMode ? StructMarshal.Color(0x33FFFFFF) : StructMarshal.Color(0x33000000);
}
public static Color BaseMediumLowColor(bool isDarkMode)
{
return isDarkMode ? StructMarshal.Color(0x66FFFFFF) : StructMarshal.Color(0x66000000);
}
public static Color BaseHighColor(bool isDarkMode)
{
return isDarkMode ? StructMarshal.Color(0xFFFFFFFF) : StructMarshal.Color(0xFF000000);
}
}

View File

@@ -30,6 +30,7 @@
<x:String x:Key="UI_EmotionIcon25">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon25.png</x:String>
<x:String x:Key="UI_EmotionIcon52">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon52.png</x:String>
<x:String x:Key="UI_EmotionIcon71">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon71.png</x:String>
<x:String x:Key="UI_EmotionIcon89">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon89.png</x:String>
<x:String x:Key="UI_EmotionIcon250">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon250.png</x:String>
<x:String x:Key="UI_EmotionIcon271">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon271.png</x:String>
<x:String x:Key="UI_EmotionIcon272">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon272.png</x:String>

View File

@@ -1,18 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Abstraction;
/// <summary>
/// 可克隆
/// </summary>
/// <typeparam name="TSelf">自身类型</typeparam>
[HighQuality]
internal interface ICloneable<out TSelf>
{
/// <summary>
/// 克隆
/// </summary>
/// <returns>新的克隆</returns>
TSelf Clone();
}

View File

@@ -32,6 +32,14 @@ internal sealed class HutaoException : Exception
}
}
public static void ThrowIfNot(bool condition, HutaoExceptionKind kind, string message, Exception? innerException = default)
{
if (!condition)
{
throw new HutaoException(kind, message, innerException);
}
}
public static HutaoException ServiceTypeCastFailed<TFrom, TTo>(string name, Exception? innerException = default)
{
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";

View File

@@ -6,8 +6,16 @@ namespace Snap.Hutao.Core.ExceptionService;
internal enum HutaoExceptionKind
{
None,
// Foundation
ServiceTypeCastFailed,
ImageCacheInvalidUri,
// IO
FileSystemCreateFileInsufficientPermissions,
PrivateNamedPipeContentHashIncorrect,
// Service
GachaStatisticsInvalidItemId,
GameFpsUnlockingFailed,
}

View File

@@ -0,0 +1,114 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Logging;
internal static class ConsoleVirtualTerminalSequences
{
public const string Default = "\u001b[0m";
public const string Bold = "\u001b[1m";
public const string Underline = "\u001b[4m";
public const string Negative = "\u001b[7m";
public const string NoBold = "\u001b[22m";
public const string NoUnderline = "\u001b[24m";
public const string Positive = "\u001b[27m";
public const string ForegroundBlack = "\u001b[30m";
public const string ForegroundRed = "\u001b[31m";
public const string ForegroundGreen = "\u001b[32m";
public const string ForegroundYellow = "\u001b[33m";
public const string ForegroundBlue = "\u001b[34m";
public const string ForegroundMagenta = "\u001b[35m";
public const string ForegroundCyan = "\u001b[36m";
public const string ForegroundWhite = "\u001b[37m";
public const string ForegroundExtended = "\u001b[38m";
public const string ForegroundDefault = "\u001b[39m";
public const string BackgroundBlack = "\u001b[40m";
public const string BackgroundRed = "\u001b[41m";
public const string BackgroundGreen = "\u001b[42m";
public const string BackgroundYellow = "\u001b[43m";
public const string BackgroundBlue = "\u001b[44m";
public const string BackgroundMagenta = "\u001b[45m";
public const string BackgroundCyan = "\u001b[46m";
public const string BackgroundWhite = "\u001b[47m";
public const string BackgroundExtended = "\u001b[48m";
public const string BackgroundDefault = "\u001b[49m";
public const string BrightForegroundBlack = "\u001b[1m\u001b[30m";
public const string BrightForegroundRed = "\u001b[1m\u001b[31m";
public const string BrightForegroundGreen = "\u001b[1m\u001b[32m";
public const string BrightForegroundYellow = "\u001b[1m\u001b[33m";
public const string BrightForegroundBlue = "\u001b[1m\u001b[34m";
public const string BrightForegroundMagenta = "\u001b[1m\u001b[35m";
public const string BrightForegroundCyan = "\u001b[1m\u001b[36m";
public const string BrightForegroundWhite = "\u001b[1m\u001b[37m";
public const string BrightBackgroundBlack = "\u001b[1m\u001b[40m";
public const string BrightBackgroundRed = "\u001b[1m\u001b[41m";
public const string BrightBackgroundGreen = "\u001b[1m\u001b[42m";
public const string BrightBackgroundYellow = "\u001b[1m\u001b[43m";
public const string BrightBackgroundBlue = "\u001b[1m\u001b[44m";
public const string BrightBackgroundMagenta = "\u001b[1m\u001b[45m";
public const string BrightBackgroundCyan = "\u001b[1m\u001b[46m";
public const string BrightBackgroundWhite = "\u001b[1m\u001b[47m";
public const string Dim = "\u001b[2m";
public const string Italic = "\u001b[3m";
public const string Blink = "\u001b[5m";
public const string Hidden = "\u001b[8m";
public const string StrikeThrough = "\u001b[9m";
public const string DoubleUnderline = "\u001b[21m";
public const string NoItalic = "\u001b[23m";
public const string NoBlink = "\u001b[25m";
public const string NoHidden = "\u001b[28m";
public const string NoStrikeThrough = "\u001b[29m";
public static string FromConsoleColor(ConsoleColor color, bool foreground)
{
return (foreground, color) switch
{
(true, ConsoleColor.Black) => ForegroundBlack,
(true, ConsoleColor.DarkBlue) => ForegroundBlue,
(true, ConsoleColor.DarkGreen) => ForegroundGreen,
(true, ConsoleColor.DarkCyan) => ForegroundCyan,
(true, ConsoleColor.DarkRed) => ForegroundRed,
(true, ConsoleColor.DarkMagenta) => ForegroundMagenta,
(true, ConsoleColor.DarkYellow) => ForegroundYellow,
(true, ConsoleColor.DarkGray) => BrightForegroundBlack,
(true, ConsoleColor.Gray) => ForegroundWhite,
(true, ConsoleColor.Blue) => BrightForegroundBlue,
(true, ConsoleColor.Green) => BrightForegroundGreen,
(true, ConsoleColor.Cyan) => BrightForegroundCyan,
(true, ConsoleColor.Red) => BrightForegroundRed,
(true, ConsoleColor.Magenta) => BrightForegroundMagenta,
(true, ConsoleColor.Yellow) => BrightForegroundYellow,
(true, ConsoleColor.White) => BrightForegroundWhite,
(false, ConsoleColor.Black) => BackgroundBlack,
(false, ConsoleColor.DarkBlue) => BackgroundBlue,
(false, ConsoleColor.DarkGreen) => BackgroundGreen,
(false, ConsoleColor.DarkCyan) => BackgroundCyan,
(false, ConsoleColor.DarkRed) => BackgroundRed,
(false, ConsoleColor.DarkMagenta) => BackgroundMagenta,
(false, ConsoleColor.DarkYellow) => BackgroundYellow,
(false, ConsoleColor.DarkGray) => BrightBackgroundBlack,
(false, ConsoleColor.Gray) => BackgroundWhite,
(false, ConsoleColor.Blue) => BrightBackgroundBlue,
(false, ConsoleColor.Green) => BrightBackgroundGreen,
(false, ConsoleColor.Cyan) => BrightBackgroundCyan,
(false, ConsoleColor.Red) => BrightBackgroundRed,
(false, ConsoleColor.Magenta) => BrightBackgroundMagenta,
(false, ConsoleColor.Yellow) => BrightBackgroundYellow,
(false, ConsoleColor.White) => BrightBackgroundWhite,
_ => string.Empty,
};
}
}

View File

@@ -22,7 +22,7 @@ internal sealed class ConsoleWindowLifeTime : IDisposable
HANDLE inputHandle = GetStdHandle(STD_HANDLE.STD_INPUT_HANDLE);
if (GetConsoleMode(inputHandle, out CONSOLE_MODE mode))
{
mode &= ~CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE;
mode &= ~CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(inputHandle, mode);
}
@@ -38,4 +38,4 @@ internal sealed class ConsoleWindowLifeTime : IDisposable
FreeConsole();
}
}
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Logging;
internal readonly struct LogArgument
{
public readonly object? Argument;
public readonly ConsoleColor? ForegroundColor;
public readonly ConsoleColor? BackgroundColor;
public LogArgument(object? argument, ConsoleColor? foreground = default, ConsoleColor? background = default)
{
Argument = argument;
ForegroundColor = foreground;
BackgroundColor = background;
}
public static implicit operator LogArgument(string argument)
{
return new(argument);
}
public static implicit operator LogArgument((object? Argument, ConsoleColor Foreground) tuple)
{
return new(tuple.Argument, tuple.Foreground);
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Logging;
internal readonly struct LogMessage
{
public readonly string Message;
public readonly ConsoleColor? ForegroundColor;
public readonly ConsoleColor? BackgroundColor;
public LogMessage(string message, ConsoleColor? foreground = default, ConsoleColor? background = default)
{
Message = message;
ForegroundColor = foreground;
BackgroundColor = background;
}
public static implicit operator LogMessage(string value)
{
return new(value);
}
public static implicit operator LogMessage((string Value, ConsoleColor? Foreground) tuple)
{
return new(tuple.Value, tuple.Foreground);
}
}

View File

@@ -0,0 +1,172 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text;
namespace Snap.Hutao.Core.Logging;
[SuppressMessage("", "SH002")]
internal static class LoggerExtension
{
public static void LogColorizedDebug(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Debug, exception, message, args);
}
public static void LogColorizedDebug(this ILogger logger, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Debug, message, args);
}
public static void LogColorizedTrace(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Trace, exception, message, args);
}
public static void LogColorizedTrace(this ILogger logger, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Trace, message, args);
}
public static void LogColorizedInformation(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Information, exception, message, args);
}
public static void LogColorizedInformation(this ILogger logger, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Information, message, args);
}
public static void LogColorizedWarning(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Warning, exception, message, args);
}
public static void LogColorizedWarning(this ILogger logger, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Warning, message, args);
}
public static void LogColorizedError(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Error, exception, message, args);
}
public static void LogColorizedError(this ILogger logger, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Error, message, args);
}
public static void LogColorizedCritical(this ILogger logger, Exception? exception, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Critical, exception, message, args);
}
public static void LogColorizedCritical(this ILogger logger, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(LogLevel.Critical, message, args);
}
public static void LogColorized(this ILogger logger, LogLevel logLevel, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(logLevel, 0, null, message, args);
}
public static void LogColorized(this ILogger logger, LogLevel logLevel, EventId eventId, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(logLevel, eventId, null, message, args);
}
public static void LogColorized(this ILogger logger, LogLevel logLevel, Exception? exception, LogMessage message, params LogArgument[] args)
{
logger.LogColorized(logLevel, 0, exception, message, args);
}
public static void LogColorized(this ILogger logger, LogLevel logLevel, EventId eventId, Exception? exception, LogMessage message, params LogArgument[] args)
{
string colorizedMessage = Colorize(message, args, out object?[] outArgs)!;
logger.Log(logLevel, eventId, exception, colorizedMessage, outArgs);
}
private static string? Colorize(LogMessage message, LogArgument[] args, out object?[] outArgs)
{
StringBuilder resultMessageBuilder = new(message.Message.Length);
ReadOnlySpan<char> messageSpan = message.Message.AsSpan();
// Message base colors
ConsoleColor? messageForeground = message.ForegroundColor;
ConsoleColor? messageBackground = message.BackgroundColor;
if (messageForeground.HasValue)
{
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(messageForeground.Value, true));
}
if (messageBackground.HasValue)
{
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(messageBackground.Value, false));
}
ReadOnlySpan<LogArgument> argSpan = args.AsSpan();
outArgs = new object?[args.Length];
int argIndex = 0;
for (int index = 0; index < messageSpan.Length; index++)
{
if (messageSpan[index] == '{')
{
ref readonly LogArgument arg = ref argSpan[argIndex];
outArgs[argIndex] = arg.Argument;
argIndex++;
if (arg.ForegroundColor.HasValue)
{
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(arg.ForegroundColor.Value, true));
}
if (arg.BackgroundColor.HasValue)
{
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(arg.BackgroundColor.Value, false));
}
int closingIndex = messageSpan[index..].IndexOf('}');
resultMessageBuilder.Append(messageSpan.Slice(index, closingIndex + 1));
index += closingIndex;
if (arg.ForegroundColor.HasValue || arg.BackgroundColor.HasValue)
{
// Restore message colors
if (messageForeground.HasValue || messageBackground.HasValue)
{
if (messageForeground.HasValue)
{
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(messageForeground.Value, true));
}
if (messageBackground.HasValue)
{
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.FromConsoleColor(messageBackground.Value, false));
}
}
else
{
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.Default);
}
}
}
else
{
resultMessageBuilder.Append(messageSpan[index]);
}
}
// Restore default colors
if (message.ForegroundColor.HasValue || message.BackgroundColor.HasValue)
{
resultMessageBuilder.Append(ConsoleVirtualTerminalSequences.Default);
}
return resultMessageBuilder.ToString();
}
}

View File

@@ -3,7 +3,7 @@
namespace Snap.Hutao.Core.Logging;
internal static class LoggerFactoryExtensions
internal static class LoggerFactoryExtension
{
public static ILoggingBuilder AddConsoleWindow(this ILoggingBuilder builder)
{

View File

@@ -9,6 +9,7 @@ namespace Snap.Hutao.Core.Validation;
/// 封装验证方法,简化微软验证
/// </summary>
[HighQuality]
[Obsolete("Use HutaoException instead")]
internal static class Must
{
/// <summary>

View File

@@ -13,6 +13,7 @@ using Snap.Hutao.Win32;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Graphics.Dwm;
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
using System.Collections.Frozen;
using System.IO;
using Windows.Graphics;
using Windows.UI;
@@ -56,25 +57,22 @@ internal sealed class WindowController
private void InitializeCore()
{
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
window.AppWindow.Title = SH.FormatAppNameAndVersion(runtimeOptions.Version);
window.AppWindow.SetIcon(Path.Combine(runtimeOptions.InstalledLocation, "Assets/Logo.ico"));
ExtendsContentIntoTitleBar();
RecoverOrInitWindowSize();
UpdateElementTheme(appOptions.ElementTheme);
UpdateImmersiveDarkMode(options.TitleBar, default!);
// appWindow.Show(true);
// appWindow.Show can't bring window to top.
window.Activate();
options.BringToForeground();
if (options.UseSystemBackdrop)
{
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
UpdateSystemBackdrop(appOptions.BackdropType);
appOptions.PropertyChanged += OnOptionsPropertyChanged;
}
UpdateSystemBackdrop(appOptions.BackdropType);
appOptions.PropertyChanged += OnOptionsPropertyChanged;
subclass.Initialize();
@@ -190,12 +188,12 @@ internal sealed class WindowController
appTitleBar.ButtonBackgroundColor = Colors.Transparent;
appTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
IAppResourceProvider resourceProvider = serviceProvider.GetRequiredService<IAppResourceProvider>();
bool isDarkMode = Control.Theme.ThemeHelper.IsDarkMode(options.TitleBar.ActualTheme);
Color systemBaseLowColor = resourceProvider.GetResource<Color>("SystemBaseLowColor");
Color systemBaseLowColor = Control.Theme.SystemColors.BaseLowColor(isDarkMode);
appTitleBar.ButtonHoverBackgroundColor = systemBaseLowColor;
Color systemBaseMediumLowColor = resourceProvider.GetResource<Color>("SystemBaseMediumLowColor");
Color systemBaseMediumLowColor = Control.Theme.SystemColors.BaseMediumLowColor(isDarkMode);
appTitleBar.ButtonPressedBackgroundColor = systemBaseMediumLowColor;
// The Foreground doesn't accept Alpha channel. So we translate it to gray.
@@ -203,7 +201,7 @@ internal sealed class WindowController
byte result = (byte)((systemBaseMediumLowColor.A / 255.0) * light);
appTitleBar.ButtonInactiveForegroundColor = Color.FromArgb(0xFF, result, result, result);
Color systemBaseHighColor = resourceProvider.GetResource<Color>("SystemBaseHighColor");
Color systemBaseHighColor = Control.Theme.SystemColors.BaseHighColor(isDarkMode);
appTitleBar.ButtonForegroundColor = systemBaseHighColor;
appTitleBar.ButtonHoverForegroundColor = systemBaseHighColor;
appTitleBar.ButtonPressedForegroundColor = systemBaseHighColor;

View File

@@ -41,21 +41,18 @@ internal readonly struct WindowOptions
/// </summary>
public readonly bool PersistSize;
public readonly bool UseSystemBackdrop;
/// <summary>
/// 是否使用 Win UI 3 自带的拓展标题栏实现
/// </summary>
public readonly bool UseLegacyDragBarImplementation = !AppWindowTitleBar.IsCustomizationSupported();
public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false, bool useSystemBackdrop = true)
public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false)
{
Hwnd = WindowNative.GetWindowHandle(window);
InputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id);
TitleBar = titleBar;
InitSize = initSize;
PersistSize = persistSize;
UseSystemBackdrop = useSystemBackdrop;
}
/// <summary>

View File

@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Service;
namespace Snap.Hutao.Factory.ContentDialog;
@@ -15,6 +16,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
private readonly ICurrentWindowReference currentWindowReference;
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
/// <inheritdoc/>
public async ValueTask<ContentDialogResult> CreateForConfirmAsync(string title, string content)
@@ -27,6 +29,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
Content = content,
DefaultButton = ContentDialogButton.Primary,
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
RequestedTheme = appOptions.ElementTheme,
};
return await dialog.ShowAsync();
@@ -44,6 +47,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
DefaultButton = defaultButton,
PrimaryButtonText = SH.ContentDialogConfirmPrimaryButtonText,
CloseButtonText = SH.ContentDialogCancelCloseButtonText,
RequestedTheme = appOptions.ElementTheme,
};
return await dialog.ShowAsync();
@@ -58,6 +62,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
XamlRoot = currentWindowReference.GetXamlRoot(),
Title = title,
Content = new ProgressBar() { IsIndeterminate = true },
RequestedTheme = appOptions.ElementTheme,
};
return dialog;
@@ -69,6 +74,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
await taskContext.SwitchToMainThreadAsync();
TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters);
contentDialog.XamlRoot = currentWindowReference.GetXamlRoot();
contentDialog.RequestedTheme = appOptions.ElementTheme;
return contentDialog;
}
@@ -77,6 +83,7 @@ internal sealed partial class ContentDialogFactory : IContentDialogFactory
{
TContentDialog contentDialog = serviceProvider.CreateInstance<TContentDialog>(parameters);
contentDialog.XamlRoot = currentWindowReference.GetXamlRoot();
contentDialog.RequestedTheme = appOptions.ElementTheme;
return contentDialog;
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Model.Entity.Configuration;
using System.Diagnostics;
@@ -34,7 +35,7 @@ internal sealed class AppDbContext : DbContext
: this(options)
{
this.logger = logger;
logger.LogInformation("{Name}[{Id}] created", nameof(AppDbContext), ContextId);
logger.LogColorizedInformation("{Name}[{Id}] {Action}", nameof(AppDbContext), (ContextId, ConsoleColor.DarkCyan), ("created", ConsoleColor.Green));
}
public DbSet<SettingEntry> Settings { get; set; } = default!;
@@ -87,7 +88,7 @@ internal sealed class AppDbContext : DbContext
public override void Dispose()
{
base.Dispose();
logger?.LogInformation("{Name}[{Id}] disposed", nameof(AppDbContext), ContextId);
logger?.LogColorizedInformation("{Name}[{Id}] {Action}", nameof(AppDbContext), (ContextId, ConsoleColor.DarkCyan), ("disposed", ConsoleColor.Red));
}
/// <inheritdoc/>

View File

@@ -17,7 +17,7 @@ internal sealed class UIGF : IJsonOnSerializing, IJsonOnDeserialized
/// <summary>
/// 当前版本
/// </summary>
public const string CurrentVersion = "v2.4";
public const string CurrentVersion = "v3.0";
/// <summary>
/// 信息
@@ -61,6 +61,7 @@ internal sealed class UIGF : IJsonOnSerializing, IJsonOnDeserialized
"v2.2" => UIGFVersion.Major2Minor2OrLower,
"v2.3" => UIGFVersion.Major2Minor3OrHigher,
"v2.4" => UIGFVersion.Major2Minor3OrHigher,
"v3.0" => UIGFVersion.Major2Minor3OrHigher,
_ => UIGFVersion.NotSupported,
};

View File

@@ -11,34 +11,26 @@ namespace Snap.Hutao.Model.Intrinsic.Frozen;
[HighQuality]
internal static class IntrinsicFrozen
{
/// <summary>
/// 所属地区
/// </summary>
public static FrozenSet<string> AssociationTypes { get; } = Enum.GetValues<AssociationType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
/// <summary>
/// 武器类型
/// </summary>
public static FrozenSet<NameValue<AssociationType>> AssociationTypeNameValues { get; } = Enum.GetValues<AssociationType>().Select(e => new NameValue<AssociationType>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
public static FrozenSet<string> WeaponTypes { get; } = Enum.GetValues<WeaponType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
/// <summary>
/// 物品类型
/// </summary>
public static FrozenSet<NameValue<WeaponType>> WeaponTypeNameValues { get; } = Enum.GetValues<WeaponType>().Select(e => new NameValue<WeaponType>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
public static FrozenSet<string> ItemQualities { get; } = Enum.GetValues<QualityType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
/// <summary>
/// 身材类型
/// </summary>
public static FrozenSet<NameValue<QualityType>> ItemQualityNameValues { get; } = Enum.GetValues<QualityType>().Select(e => new NameValue<QualityType>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
public static FrozenSet<string> BodyTypes { get; } = Enum.GetValues<BodyType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
/// <summary>
/// 战斗属性
/// </summary>
public static FrozenSet<NameValue<BodyType>> BodyTypeNameValues { get; } = Enum.GetValues<BodyType>().Select(e => new NameValue<BodyType>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
public static FrozenSet<string> FightProperties { get; } = Enum.GetValues<FightProperty>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToFrozenSet();
/// <summary>
/// 元素名称
/// </summary>
public static FrozenSet<NameValue<FightProperty>> FightPropertyNameValues { get; } = Enum.GetValues<FightProperty>().Select(e => new NameValue<FightProperty>(e.GetLocalizedDescriptionOrDefault()!, e)).Where(nv => !string.IsNullOrEmpty(nv.Name)).ToFrozenSet();
public static FrozenSet<string> ElementNames { get; } = FrozenSet.ToFrozenSet(
[
SH.ModelIntrinsicElementNameFire,
@@ -50,6 +42,17 @@ internal static class IntrinsicFrozen
SH.ModelIntrinsicElementNameRock,
]);
public static FrozenSet<NameValue<int>> ElementNameValues { get; } = FrozenSet.ToFrozenSet(
[
new NameValue<int>(SH.ModelIntrinsicElementNameFire, 1),
new NameValue<int>(SH.ModelIntrinsicElementNameWater, 2),
new NameValue<int>(SH.ModelIntrinsicElementNameGrass, 3),
new NameValue<int>(SH.ModelIntrinsicElementNameElec, 4),
new NameValue<int>(SH.ModelIntrinsicElementNameWind, 5),
new NameValue<int>(SH.ModelIntrinsicElementNameIce, 6),
new NameValue<int>(SH.ModelIntrinsicElementNameRock, 7),
]);
public static FrozenSet<string> MaterialTypeDescriptions { get; } = FrozenSet.ToFrozenSet(
[
SH.ModelMetadataMaterialCharacterAndWeaponEnhancementMaterial,

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata;
// CityTaskOpenExcelConfig
internal enum City : uint
{
Mondstadt = 1,
Liyue = 2,
Inazuma = 3,
Sumeru = 4,
Fontaine = 5,
Natlan,
Snezhnaya,
}

View File

@@ -9,24 +9,6 @@ namespace Snap.Hutao.Model.Metadata.Converter;
internal sealed class AssociationTypeIconConverter : ValueConverter<AssociationType, Uri?>
{
private static readonly FrozenDictionary<string, AssociationType> LocalizedNameToAssociationType = FrozenDictionary.ToFrozenDictionary(
[
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeMondstadt, AssociationType.ASSOC_TYPE_MONDSTADT),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeLiyue, AssociationType.ASSOC_TYPE_LIYUE),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFatui, AssociationType.ASSOC_TYPE_FATUI),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeInazuma, AssociationType.ASSOC_TYPE_INAZUMA),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeRanger, AssociationType.ASSOC_TYPE_RANGER),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSumeru, AssociationType.ASSOC_TYPE_SUMERU),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeFontaine, AssociationType.ASSOC_TYPE_FONTAINE),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeNatlan, AssociationType.ASSOC_TYPE_NATLAN),
KeyValuePair.Create(SH.ModelIntrinsicAssociationTypeSnezhnaya, AssociationType.ASSOC_TYPE_SNEZHNAYA),
]);
public static Uri? AssociationTypeNameToIconUri(string associationTypeName)
{
return AssociationTypeToIconUri(LocalizedNameToAssociationType.GetValueOrDefault(associationTypeName));
}
public static Uri? AssociationTypeToIconUri(AssociationType type)
{
string? association = type switch

View File

@@ -21,6 +21,7 @@ internal static class MonsterRelationship
5112U => 511U, // 历经百战的浊水喷吐幻灵
30605U => 30603U, // 历经百战的霜剑律从
30606U => 30604U, // 历经百战的幽风铃兰
40632U => 40613U, // 自律超算型场力发生装置
60402U => 60401U, // (火)岩龙蜥
60403U => 60401U, // (冰)岩龙蜥
60404U => 60401U, // (雷)岩龙蜥

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutao"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.7.0" />
Version="1.9.8.0" />
<Properties>
<DisplayName>Snap Hutao</DisplayName>

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutaoDev"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.7.0" />
Version="1.9.8.0" />
<Properties>
<DisplayName>Snap Hutao Dev</DisplayName>

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -144,6 +144,9 @@
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
<value>No results found</value>
</data>
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
<value>Invalid Uri</value>
</data>
@@ -186,6 +189,9 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>No WebView2 Runtime detected</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>Register [{0}] hotkey [{1}] failed</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>Dark</value>
</data>
@@ -866,6 +872,9 @@
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>Character Event Wish</value>
</data>
<data name="ServiceGachaLogFactoryChronicledWishName" xml:space="preserve">
<value>Chronicled Wish</value>
</data>
<data name="ServiceGachaLogFactoryPermanentWishName" xml:space="preserve">
<value>Wanderlust Invocation</value>
</data>
@@ -2223,7 +2232,7 @@
<value>Auto start Better GUI for automation tasks after game launched</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
<value>Automated Tasks</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>General</value>
@@ -2241,7 +2250,7 @@
<value>File</value>
</data>
<data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve">
<value>InterProcess</value>
<value>Process Linkage</value>
</data>
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
<value>Run the software on the selected monitor</value>
@@ -2262,7 +2271,7 @@
<value>Try to start Starward after the game is started for game duration statistics</value>
</data>
<data name="ViewPageLaunchGamePlayTimeHeader" xml:space="preserve">
<value>Hours Played</value>
<value>Game Hours Record</value>
</data>
<data name="ViewPageLaunchGameProcessHeader" xml:space="preserve">
<value>Progress</value>
@@ -2375,6 +2384,9 @@
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>Wallpaper Image</value>
</data>
<data name="ViewPageSettingBackgroundImageLocalFolderCopyrightHeader" xml:space="preserve">
<value>Using local images as the background</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>Images cache are saved here</value>
</data>
@@ -2630,6 +2642,12 @@
<data name="ViewPageSettingStoreReviewNavigate" xml:space="preserve">
<value>Rate Snap Hutao</value>
</data>
<data name="ViewPageSettingThemeDescription" xml:space="preserve">
<value>Change window theme</value>
</data>
<data name="ViewPageSettingThemeHeader" xml:space="preserve">
<value>Theme</value>
</data>
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>Contribute Translations</value>
</data>
@@ -2639,6 +2657,12 @@
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>Webview2 Runtime</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceDownHeader" xml:space="preserve">
<value>Second Half</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceUpHeader" xml:space="preserve">
<value>First Half</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>Artifact Set Combination</value>
</data>
@@ -2745,7 +2769,7 @@
<value>Character Appearance Rate = Character Appearance in this Floor (only count for 1 if repeated) / Total Number of Abyss Record of this Floor</value>
</data>
<data name="ViewSpiralAbyssAvatarUsageRankDescription" xml:space="preserve">
<value>Character Usage Rate = Character Appearance in this Floor (only count for 1 is repeated) / Number of Player who Own this Character</value>
<value>Character Usage Rate = Character Appearance in this Floor (only count for 1 if repeated) / Number of Player who Own this Character</value>
</data>
<data name="ViewSpiralAbyssBattleHeader" xml:space="preserve">
<value>Battle Statistics</value>
@@ -2901,7 +2925,7 @@
<value>〓Event Duration〓.*?\d\.\d Available throughout the entirety of Version</value>
</data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓Event Duration〓|Event Wish Duration|【Availability Duration】).*?(\d\.\dAfter the Version update).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
<value>(?:〓Event Duration〓|Event Wish Duration|【Availability Duration】|〓Discount Period〓).*?(\d\.\dAfter the Version update).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓Update Maintenance Duration〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
@@ -3053,6 +3077,9 @@
<data name="WebGachaConfigTypeAvatarEventWish2" xml:space="preserve">
<value>Character Event Wish-2</value>
</data>
<data name="WebGachaConfigTypeChronicledWish" xml:space="preserve">
<value>Chronicled Wish</value>
</data>
<data name="WebGachaConfigTypeNoviceWish" xml:space="preserve">
<value>Beginners' Wish</value>
</data>

View File

@@ -186,6 +186,18 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>WebView2 Runtime tidak terdeteksi.</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] Hotkey [{1}] gagal terdaftar</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>深色</value>
</data>
<data name="CoreWindowThemeLight" xml:space="preserve">
<value>浅色</value>
</data>
<data name="CoreWindowThemeSystem" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>Ekspor</value>
</data>
@@ -199,19 +211,19 @@
<value>Pilih akun untuk memulai</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlD" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;王子&lt;/color&gt;/&lt;color=#FFB6C1&gt;公主&lt;/color&gt;</value>
<value>&lt;color=#1E90FF&gt;Prince&lt;/color&gt;/&lt;color=#FFB6C1&gt;Princess&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameMetaAvatarSexProInfoPronounBoyGirlFirst" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;&lt;/color&gt;/&lt;color=#FFB6C1&gt;&lt;/color&gt;</value>
<value>&lt;color=#1E90FF&gt;I&lt;/color&gt;/&lt;color=#FFB6C1&gt;I&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>Pengembara</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;&lt;/color&gt;/&lt;color=#FFB6C1&gt;&lt;/color&gt;</value>
<value>&lt;color=#1E90FF&gt;He&lt;/color&gt;/&lt;color=#FFB6C1&gt;She&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameRealNameId1" xml:space="preserve">
<value>流浪者</value>
<value>Pengembara</value>
</data>
<data name="ModelBindingAvatarPropertyWeaponAffixFormat" xml:space="preserve">
<value>Perbaiki {0}</value>
@@ -513,7 +525,7 @@
<value>Gelombang 4: Gelombang akan muncul hanya setelah membunuh semua musuh di gelombang sebelumnya</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>维持场上 4 个盗宝团怪物,击杀后立即替换,总数 12</value>
<value>Tetapkan 4 Perampok Harta Karun di lapangan, langsung muncul kembali setelah mati. Total 12.</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>Silakan perbarui data tampilan.</value>
@@ -768,19 +780,19 @@
<value>Pameran Karakter: {0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
<value>Wallpaper Harian Bing</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
<value>Wallpaper Harian Hutao</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
<value>Wallpaper Resmi Peluncur Genshin</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
<value>Gambar Acak Lokal</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
<value>Tanpa Gambar Latar</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>Gagal menyimpan status rencana pengembangan</value>
@@ -840,7 +852,7 @@
<value>Parametric Transformer telah siap</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>权限不足,将无法为您设置 Discord Activity 状态</value>
<value>Izin hilang, tidak dapat mengatur Aktivitas Discord Anda.</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>Menjelajahi di Teyvat</value>
@@ -857,6 +869,9 @@
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>Event Wish Karakter</value>
</data>
<data name="ServiceGachaLogFactoryChronicledWishName" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="ServiceGachaLogFactoryPermanentWishName" xml:space="preserve">
<value>Wanderlust Invocation</value>
</data>
@@ -1224,10 +1239,10 @@
<value>Catatan Realtime Webhook URL</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
<value>解除限制后需要使用其他工具恢复限制</value>
<value>Anda memerlukan alat lain untuk mengembalikan loopback setelah dikecualikan</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
<value>是否解除 Loopback 限制</value>
<value>Konfirmasi untuk Mengonfigurasi Pengecualian Loopback</value>
</data>
<data name="ViewDialogGachaLogImportTitle" xml:space="preserve">
<value>Mengimpor riwayat wish</value>
@@ -1410,7 +1425,7 @@
<value>Game Launcher</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理员模式下无法拖动排序</value>
<value>Tidak dapat mengurutkan dalam Mode Administrator</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>Arsip [{0}] berhasil ditambahkan</value>
@@ -1692,7 +1707,7 @@
<value>Selesai</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
<value>Stream respons tidak berisi tipe konten yang valid</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>Mengantre</value>
@@ -1911,16 +1926,16 @@
<value>Tautan Berguna</value>
</data>
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
<value>当前代理</value>
<value>Proxy Saat ini</value>
</data>
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
<value>无代理</value>
<value>Tidak ada Proxy</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
<value>已解除</value>
<value>Dikecualikan</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
<value>解除 Loopback 限制</value>
<value>Konfigurasikan Pengecualian Loopback</value>
</data>
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
<value>Tetap berhubungan dengan kami</value>
@@ -2211,10 +2226,10 @@
<value>Argumen Awalan</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
<value>Mulai Otomatis Antarmuka Pengguna yang Lebih Baik untuk tugas otomatis setelah game diluncurkan</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
<value>自动化任务</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>Umum</value>
@@ -2358,13 +2373,16 @@
<value>Backdrop Material</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>图片版权信息</value>
<value>Informasi Hak Cipta Gambar</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
<value>Ubah sumber gambar latar, restart Snap Hutao untuk menerapkan perubahan</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
<value>Gambar Latar</value>
</data>
<data name="ViewPageSettingBackgroundImageLocalFolderCopyrightHeader" xml:space="preserve">
<value>当前背景为本地图片,如遇版权问题由用户个人负责</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>Cache gambar disimpan di sini</value>
@@ -2373,7 +2391,7 @@
<value>Berkas Cache</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行证</value>
<value>Hutao Passport</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>Salin</value>
@@ -2568,10 +2586,10 @@
<value>Website Resmi</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
<value>Latar Belakang Kustom, format didukung bmp/gif/ico/jpg/jpeg/png/tiff/webp</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打开背景图片文件夹</value>
<value>Buka Berkas Latar Belakang</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>Reset</value>
@@ -2583,10 +2601,10 @@
<value>Setel ulang Sumber Daya Gambar</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
<value>Tambahkan Opsi Batasan FPS di Bagian Proses Peluncur Permainan</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
<value>Peluncur Permainan - Buka Limit FPS</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>Anda perlu memindahkan data di direktori secara manual, jika tidak, data pengguna baru akan dibuat.</value>
@@ -2621,6 +2639,12 @@
<data name="ViewPageSettingStoreReviewNavigate" xml:space="preserve">
<value>Beri Rating untuk Snap Hutao</value>
</data>
<data name="ViewPageSettingThemeDescription" xml:space="preserve">
<value>更改窗体的颜色主题</value>
</data>
<data name="ViewPageSettingThemeHeader" xml:space="preserve">
<value>颜色主题</value>
</data>
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>Kontribusi penerjemahan</value>
</data>
@@ -2630,6 +2654,12 @@
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>Webview2 Runtime</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceDownHeader" xml:space="preserve">
<value>下半</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceUpHeader" xml:space="preserve">
<value>上半</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>Kombinasi Set Artefak</value>
</data>
@@ -2892,7 +2922,7 @@
<value>〓Durasi Event〓.*?\d\.\d Tersedia selama versi ini</value>
</data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓Durasi Event〓|Durasi Event Wish|【Ketersediaan DUrasi】).*?(\d\.\dSetelah pembaruan versi).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
<value>(?:〓Waktu Acara〓|Waktu Menginginkan|【Waktu Peluncuran】|〓Waktu Diskon〓).*?(\d\.\d Setelah Pembaruan Versi).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓Durasi Pemeliharaan Pembaruan.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
@@ -3044,6 +3074,9 @@
<data name="WebGachaConfigTypeAvatarEventWish2" xml:space="preserve">
<value>Event WIsh Karakter-2</value>
</data>
<data name="WebGachaConfigTypeChronicledWish" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="WebGachaConfigTypeNoviceWish" xml:space="preserve">
<value>Wish Pemula</value>
</data>

View File

@@ -186,6 +186,18 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>WebView2 ランタイムが検出されませんでした</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] 热键 [{1}] 注册失败</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>深色</value>
</data>
<data name="CoreWindowThemeLight" xml:space="preserve">
<value>浅色</value>
</data>
<data name="CoreWindowThemeSystem" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>エクスポート</value>
</data>
@@ -857,6 +869,9 @@
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>イベント祈願・キャラクター</value>
</data>
<data name="ServiceGachaLogFactoryChronicledWishName" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="ServiceGachaLogFactoryPermanentWishName" xml:space="preserve">
<value>奔走世間</value>
</data>
@@ -1353,7 +1368,7 @@
<value>クッキーを設定</value>
</data>
<data name="ViewFeedbackHeader" xml:space="preserve">
<value>フィードバック センター</value>
<value>フィードバックセンター</value>
</data>
<data name="ViewGachaLogHeader" xml:space="preserve">
<value>祈願履歴</value>
@@ -2214,7 +2229,7 @@
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
<value>自动化任务</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>一般</value>
@@ -2366,6 +2381,9 @@
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingBackgroundImageLocalFolderCopyrightHeader" xml:space="preserve">
<value>当前背景为本地图片,如遇版权问题由用户个人负责</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>イメージキャッシュはここに格納されます</value>
</data>
@@ -2487,7 +2505,7 @@
<value>アチーブメント</value>
</data>
<data name="ViewpageSettingHomeCardItemDailyNoteHeader" xml:space="preserve">
<value>リアルタイムノート</value>
<value>リアルタイムノート</value>
</data>
<data name="ViewpageSettingHomeCardItemgachaStatisticsHeader" xml:space="preserve">
<value>祈願履歴</value>
@@ -2621,6 +2639,12 @@
<data name="ViewPageSettingStoreReviewNavigate" xml:space="preserve">
<value>レビューや感想</value>
</data>
<data name="ViewPageSettingThemeDescription" xml:space="preserve">
<value>更改窗体的颜色主题</value>
</data>
<data name="ViewPageSettingThemeHeader" xml:space="preserve">
<value>颜色主题</value>
</data>
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>和訳を提供</value>
</data>
@@ -2630,6 +2654,12 @@
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>Webview2 ランタイム</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceDownHeader" xml:space="preserve">
<value>下半</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceUpHeader" xml:space="preserve">
<value>上半</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>おすすめ聖遺物</value>
</data>
@@ -2829,7 +2859,7 @@
<value>QRコードでログイン</value>
</data>
<data name="ViewUserCookieOperationManualInputAction" xml:space="preserve">
<value>手入力</value>
<value>手入力</value>
</data>
<data name="ViewUserCookieOperationRefreshCookieAction" xml:space="preserve">
<value>Cookie 再取得</value>
@@ -2895,10 +2925,10 @@
<value>(?:〓イベント期間〓|祈願期間|【開始日時】).*?(\d\.\dバージョンアップ完了後).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新日時〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
<value>〓メンテナンス時間〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdateTitle" xml:space="preserve">
<value>Ver.\d\.\d 更新内容</value>
<value>Ver.\d\.\d.+正式リリース</value>
</data>
<data name="WebAnnouncementTimeDaysBeginFormat" xml:space="preserve">
<value>{0} 日後に開始</value>
@@ -3044,6 +3074,9 @@
<data name="WebGachaConfigTypeAvatarEventWish2" xml:space="preserve">
<value>イベント祈願・キャラクター2</value>
</data>
<data name="WebGachaConfigTypeChronicledWish" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="WebGachaConfigTypeNoviceWish" xml:space="preserve">
<value>初心者祈願</value>
</data>

View File

@@ -186,6 +186,18 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>WebView2 런타임이 감지되지 않음</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] 热键 [{1}] 注册失败</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>深色</value>
</data>
<data name="CoreWindowThemeLight" xml:space="preserve">
<value>浅色</value>
</data>
<data name="CoreWindowThemeSystem" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>내보내기</value>
</data>
@@ -857,6 +869,9 @@
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>캐릭터 이벤트</value>
</data>
<data name="ServiceGachaLogFactoryChronicledWishName" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="ServiceGachaLogFactoryPermanentWishName" xml:space="preserve">
<value>세상 여행</value>
</data>
@@ -2214,7 +2229,7 @@
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
<value>自动化任务</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>보통</value>
@@ -2366,6 +2381,9 @@
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingBackgroundImageLocalFolderCopyrightHeader" xml:space="preserve">
<value>当前背景为本地图片,如遇版权问题由用户个人负责</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>여기에 저장된 이미지 캐시</value>
</data>
@@ -2621,6 +2639,12 @@
<data name="ViewPageSettingStoreReviewNavigate" xml:space="preserve">
<value>评价软件</value>
</data>
<data name="ViewPageSettingThemeDescription" xml:space="preserve">
<value>更改窗体的颜色主题</value>
</data>
<data name="ViewPageSettingThemeHeader" xml:space="preserve">
<value>颜色主题</value>
</data>
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>번역에 기여하기</value>
</data>
@@ -2630,6 +2654,12 @@
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>Webview2 런타임</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceDownHeader" xml:space="preserve">
<value>下半</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceUpHeader" xml:space="preserve">
<value>上半</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>搭配圣遗物</value>
</data>
@@ -3044,6 +3074,9 @@
<data name="WebGachaConfigTypeAvatarEventWish2" xml:space="preserve">
<value>캐릭터 이벤트 기원-2</value>
</data>
<data name="WebGachaConfigTypeChronicledWish" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="WebGachaConfigTypeNoviceWish" xml:space="preserve">
<value>초심자 기원</value>
</data>

View File

@@ -186,6 +186,18 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>O WebView2 Runtime não foi detectado</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] 热键 [{1}] 注册失败</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>深色</value>
</data>
<data name="CoreWindowThemeLight" xml:space="preserve">
<value>浅色</value>
</data>
<data name="CoreWindowThemeSystem" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>Exportar</value>
</data>
@@ -857,6 +869,9 @@
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>Evento de oração de personagem</value>
</data>
<data name="ServiceGachaLogFactoryChronicledWishName" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="ServiceGachaLogFactoryPermanentWishName" xml:space="preserve">
<value>Invocação do Mochileiro</value>
</data>
@@ -1122,7 +1137,7 @@
<value>Sorte média</value>
</data>
<data name="ViewControlStatisticsCardUpText" xml:space="preserve">
<value>Acima</value>
<value>Em destaque</value>
</data>
<data name="ViewControlStatisticsSegmentedItemContentPrediction" xml:space="preserve">
<value>Previsão</value>
@@ -1140,7 +1155,7 @@
<value>Planejamento</value>
</data>
<data name="ViewDailyNoteHeader" xml:space="preserve">
<value>Lembrete em tempo real</value>
<value>Lembretes</value>
</data>
<data name="ViewDataHeader" xml:space="preserve">
<value>Dados</value>
@@ -2034,7 +2049,7 @@
<value>Visão geral</value>
</data>
<data name="ViewPageGahcaLogPivotStatistics" xml:space="preserve">
<value>全球祈愿统计</value>
<value>Estatísticas globais</value>
</data>
<data name="ViewPageGahcaLogPivotWeapon" xml:space="preserve">
<value>Arma</value>
@@ -2214,7 +2229,7 @@
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
<value>自动化任务</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>Geral</value>
@@ -2366,6 +2381,9 @@
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingBackgroundImageLocalFolderCopyrightHeader" xml:space="preserve">
<value>当前背景为本地图片,如遇版权问题由用户个人负责</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>O cache de imagens é salvo aqui</value>
</data>
@@ -2621,6 +2639,12 @@
<data name="ViewPageSettingStoreReviewNavigate" xml:space="preserve">
<value>Avalie o Snap Hutao</value>
</data>
<data name="ViewPageSettingThemeDescription" xml:space="preserve">
<value>更改窗体的颜色主题</value>
</data>
<data name="ViewPageSettingThemeHeader" xml:space="preserve">
<value>颜色主题</value>
</data>
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>Contribuir com traduções</value>
</data>
@@ -2630,6 +2654,12 @@
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>Webview2 Runtime</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceDownHeader" xml:space="preserve">
<value>下半</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceUpHeader" xml:space="preserve">
<value>上半</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>Combinação de conjuntos de artefatos</value>
</data>
@@ -2961,7 +2991,7 @@
<value>Recompensa de comissão diária resgatada</value>
</data>
<data name="WebDailyNoteHomeCoinRecoveryFormat" xml:space="preserve">
<value>Estará cheio em {0} {1:HH:mm}</value>
<value>Estará cheio {0} às {1:HH:mm}</value>
</data>
<data name="WebDailyNoteHomeLocked" xml:space="preserve">
<value>Bule de Relachá não desbloqueado</value>
@@ -3044,6 +3074,9 @@
<data name="WebGachaConfigTypeAvatarEventWish2" xml:space="preserve">
<value>Evento de oração de personagem-2</value>
</data>
<data name="WebGachaConfigTypeChronicledWish" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="WebGachaConfigTypeNoviceWish" xml:space="preserve">
<value>Oração de Novatos</value>
</data>

View File

@@ -144,6 +144,9 @@
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
<value>保存</value>
</data>
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
<value>未找到结果</value>
</data>
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
<value>无效的 Uri</value>
</data>
@@ -2381,6 +2384,9 @@
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingBackgroundImageLocalFolderCopyrightHeader" xml:space="preserve">
<value>当前背景为本地图片,如遇版权问题由用户个人负责</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>图片缓存 在此处存放</value>
</data>

View File

@@ -186,6 +186,18 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>Среда выполнения WebView2 не обнаружена</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] 热键 [{1}] 注册失败</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>深色</value>
</data>
<data name="CoreWindowThemeLight" xml:space="preserve">
<value>浅色</value>
</data>
<data name="CoreWindowThemeSystem" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>Экспорт</value>
</data>
@@ -857,6 +869,9 @@
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>角色活动</value>
</data>
<data name="ServiceGachaLogFactoryChronicledWishName" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="ServiceGachaLogFactoryPermanentWishName" xml:space="preserve">
<value>奔行世间</value>
</data>
@@ -2214,7 +2229,7 @@
<value>在游戏启动后尝试启动并使用 Better GI 进行自动化任务</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
<value>自动化任务</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>常规</value>
@@ -2366,6 +2381,9 @@
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingBackgroundImageLocalFolderCopyrightHeader" xml:space="preserve">
<value>当前背景为本地图片,如遇版权问题由用户个人负责</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>图片缓存 在此处存放</value>
</data>
@@ -2621,6 +2639,12 @@
<data name="ViewPageSettingStoreReviewNavigate" xml:space="preserve">
<value>评价软件</value>
</data>
<data name="ViewPageSettingThemeDescription" xml:space="preserve">
<value>更改窗体的颜色主题</value>
</data>
<data name="ViewPageSettingThemeHeader" xml:space="preserve">
<value>颜色主题</value>
</data>
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>贡献翻译</value>
</data>
@@ -2630,6 +2654,12 @@
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>Webview2 Runtime</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceDownHeader" xml:space="preserve">
<value>下半</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceUpHeader" xml:space="preserve">
<value>上半</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>搭配圣遗物</value>
</data>
@@ -3044,6 +3074,9 @@
<data name="WebGachaConfigTypeAvatarEventWish2" xml:space="preserve">
<value>Молитва события персонажа - 2</value>
</data>
<data name="WebGachaConfigTypeChronicledWish" xml:space="preserve">
<value>集录祈愿</value>
</data>
<data name="WebGachaConfigTypeNoviceWish" xml:space="preserve">
<value>Молитва новичка</value>
</data>

View File

@@ -186,6 +186,18 @@
<data name="CoreWebView2HelperVersionUndetected" xml:space="preserve">
<value>未檢測到 WebView2 運行時</value>
</data>
<data name="CoreWindowHotkeyCombinationRegisterFailed" xml:space="preserve">
<value>[{0}] 鍵盤快速鍵 [{1}] 註冊失敗</value>
</data>
<data name="CoreWindowThemeDark" xml:space="preserve">
<value>深色</value>
</data>
<data name="CoreWindowThemeLight" xml:space="preserve">
<value>淺色</value>
</data>
<data name="CoreWindowThemeSystem" xml:space="preserve">
<value>跟隨系統</value>
</data>
<data name="FilePickerExportCommit" xml:space="preserve">
<value>匯出</value>
</data>
@@ -205,7 +217,7 @@
<value>&lt;color=#1E90FF&gt;我&lt;/color&gt;/&lt;color=#FFB6C1&gt;我&lt;/color&gt;</value>
</data>
<data name="MetadataSpecialNameNickname" xml:space="preserve">
<value>旅</value>
<value>旅行者</value>
</data>
<data name="MetadataSpecialNamePlayerAvatarSexProInfoPronounHeShe" xml:space="preserve">
<value>&lt;color=#1E90FF&gt;他&lt;/color&gt;/&lt;color=#FFB6C1&gt;她&lt;/color&gt;</value>
@@ -513,7 +525,7 @@
<value>第四波:擊敗所有怪物,下一波才會出現</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave99999" xml:space="preserve">
<value>维持场上 4 个盗宝团怪物,击杀后立即替换,总数 12 </value>
<value>維持場上 4 個盜寶團怪物,擊殺後立即替換,總數 12 </value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>請更新角色櫥窗數據</value>
@@ -768,19 +780,19 @@
<value>角色櫥窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一</value>
<value>Bing每日一</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一</value>
<value>胡桃每日一</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁</value>
<value>官方啟動器壁</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
<value>本地隨機圖片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>背景片</value>
<value>背景片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>保存養成計劃狀態失敗</value>
@@ -840,7 +852,7 @@
<value>參量質變儀已準備完成</value>
</data>
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
<value>限不足,将无法为您设置 Discord Activity 状态</value>
<value>限不足,將無法為您設定 Discord Activity 狀態</value>
</data>
<data name="ServiceDiscordGameActivityDetails" xml:space="preserve">
<value>正在提瓦特大陸中探索</value>
@@ -857,6 +869,9 @@
<data name="ServiceGachaLogFactoryAvatarWishName" xml:space="preserve">
<value>角色活動</value>
</data>
<data name="ServiceGachaLogFactoryChronicledWishName" xml:space="preserve">
<value>集錄祈願</value>
</data>
<data name="ServiceGachaLogFactoryPermanentWishName" xml:space="preserve">
<value>奔行世間</value>
</data>
@@ -1224,7 +1239,7 @@
<value>即時便箋 Webhook Url</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
<value>解除限制后需要使用其他工具恢限制</value>
<value>解除限制後需使用其他工具恢限制</value>
</data>
<data name="ViewDialogFeedbackEnableLoopbackTitle" xml:space="preserve">
<value>是否解除 Loopback 限制</value>
@@ -1410,7 +1425,7 @@
<value>啟動遊戲</value>
</data>
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
<value>管理模式下法拖排序</value>
<value>管理模式下法拖排序</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>存檔 [{0}] 添加成功</value>
@@ -1692,7 +1707,7 @@
<value>完成</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryContentTypeNotMatch" xml:space="preserve">
<value>响应内容不是有效的文件字节流</value>
<value>回應內容不是有效的檔案位元組</value>
</data>
<data name="ViewModelWelcomeDownloadSummaryDefault" xml:space="preserve">
<value>待處理</value>
@@ -1911,16 +1926,16 @@
<value>常用連結</value>
</data>
<data name="ViewPageFeedbackCurrentProxyHeader" xml:space="preserve">
<value>前代理</value>
<value>前代理</value>
</data>
<data name="ViewPageFeedbackCurrentProxyNoProxyDescription" xml:space="preserve">
<value>代理</value>
<value>代理</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackEnabledDescription" xml:space="preserve">
<value>已解除</value>
</data>
<data name="ViewPageFeedbackEnableLoopbackHeader" xml:space="preserve">
<value>解除 Loopback 限制</value>
<value>是否解除 Loopback 限制</value>
</data>
<data name="ViewPageFeedbackEngageWithUsDescription" xml:space="preserve">
<value>與我們密切聯繫</value>
@@ -2211,10 +2226,10 @@
<value>啟動參數</value>
</data>
<data name="ViewPageLaunchGameBetterGIDescription" xml:space="preserve">
<value>在游戏启动后尝试启动并使用 Better GI 行自化任</value>
<value>在遊戲啟動後嘗試啟動並使用 Better GI 行自化任</value>
</data>
<data name="ViewPageLaunchGameBetterGIHeader" xml:space="preserve">
<value>Better GI</value>
<value>自動化任務</value>
</data>
<data name="ViewPageLaunchGameCommonHeader" xml:space="preserve">
<value>一般</value>
@@ -2232,7 +2247,7 @@
<value>檔案</value>
</data>
<data name="ViewPageLaunchGameInterProcessHeader" xml:space="preserve">
<value>行程間</value>
<value>進程聯動</value>
</data>
<data name="ViewPageLaunchGameMonitorsDescription" xml:space="preserve">
<value>在指定的屏幕上運行</value>
@@ -2358,13 +2373,16 @@
<value>背景材質</value>
</data>
<data name="ViewPageSettingBackgroundImageCopyrightHeader" xml:space="preserve">
<value>片版权信息</value>
<value>片版權訊息</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗的背景图片来源,重胡桃以快生效</value>
<value>更改窗的背景圖片來源,重胡桃以快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景片</value>
<value>背景片</value>
</data>
<data name="ViewPageSettingBackgroundImageLocalFolderCopyrightHeader" xml:space="preserve">
<value>當前背景為本地圖片,如遇版權問題由用戶個人負責</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>圖片暫存存放在此</value>
@@ -2373,7 +2391,7 @@
<value>暫存檔案夾</value>
</data>
<data name="ViewPageSettingCardHutaoPassportHeader" xml:space="preserve">
<value>胡桃通行</value>
<value>胡桃通行</value>
</data>
<data name="ViewPageSettingCopyDeviceIdAction" xml:space="preserve">
<value>複製</value>
@@ -2568,10 +2586,10 @@
<value>前往官網</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderDescription" xml:space="preserve">
<value>自定背景片,支 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
<value>自定背景片,支 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式</value>
</data>
<data name="ViewPageSettingOpenBackgroundImageFolderHeader" xml:space="preserve">
<value>打背景图片文件夹</value>
<value>打背景圖片資料夾</value>
</data>
<data name="ViewPageSettingResetAction" xml:space="preserve">
<value>重設</value>
@@ -2583,10 +2601,10 @@
<value>重設圖片資源</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
<value>在启动游戏页面的进程部分加入解锁帧率限制选项</value>
<value>在啟動遊戲頁面的程序部分加入解鎖幀率限制選項</value>
</data>
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
<value>启动游戏-解锁帧率限制</value>
<value>啟動遊戲-解鎖幀率限制</value>
</data>
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
<value>更改目錄后需要手動移動目錄内的數據,否則會重新創建用戶數據</value>
@@ -2621,6 +2639,12 @@
<data name="ViewPageSettingStoreReviewNavigate" xml:space="preserve">
<value>評價軟體</value>
</data>
<data name="ViewPageSettingThemeDescription" xml:space="preserve">
<value>更改窗體的顏色主題</value>
</data>
<data name="ViewPageSettingThemeHeader" xml:space="preserve">
<value>顏色主題</value>
</data>
<data name="ViewPageSettingTranslateNavigate" xml:space="preserve">
<value>貢獻翻譯</value>
</data>
@@ -2630,6 +2654,12 @@
<data name="ViewPageSettingWebview2Header" xml:space="preserve">
<value>WebView 2 運行時</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceDownHeader" xml:space="preserve">
<value>下半</value>
</data>
<data name="ViewPageSpiralAbyssTeamAppearanceUpHeader" xml:space="preserve">
<value>上半</value>
</data>
<data name="ViewPageWiKiAvatarArtifactSetCombinationHeader" xml:space="preserve">
<value>搭配聖遺物</value>
</data>
@@ -2892,7 +2922,7 @@
<value>〓活動時間〓.*?\d\.\d版本期間持續開放</value>
</data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活動時間〓|祈願時間|【上架時間】).*?(\d\.\d版本更新後).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
<value>(?:〓活動時間〓|祈願時間|【上架時間】|〓折扣時間〓).*?(\d\.\d版本更新後).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新時間〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
@@ -3044,6 +3074,9 @@
<data name="WebGachaConfigTypeAvatarEventWish2" xml:space="preserve">
<value>角色活動祈願-2</value>
</data>
<data name="WebGachaConfigTypeChronicledWish" xml:space="preserve">
<value>集錄祈願</value>
</data>
<data name="WebGachaConfigTypeNoviceWish" xml:space="preserve">
<value>新手祈願</value>
</data>

View File

@@ -18,9 +18,9 @@ internal sealed class GachaTypeComparer : IComparer<GachaType>
KeyValuePair.Create(GachaType.ActivityAvatar, 0),
KeyValuePair.Create(GachaType.SpecialActivityAvatar, 1),
KeyValuePair.Create(GachaType.ActivityWeapon, 2),
KeyValuePair.Create(GachaType.Standard, 3),
KeyValuePair.Create(GachaType.NewBie, 4),
KeyValuePair.Create(GachaType.ActivityCity, 5),
KeyValuePair.Create(GachaType.ActivityCity, 3),
KeyValuePair.Create(GachaType.Standard, 4),
KeyValuePair.Create(GachaType.NewBie, 5),
]);
/// <summary>

View File

@@ -38,6 +38,10 @@ internal sealed class PullPrediction
typedWishSummary.PredictedPullLeftToOrange = result.PredictedPullLeftToOrange;
typedWishSummary.IsPredictPullAvailable = true;
}
else
{
await barrier.SignalAndWaitAsync().ConfigureAwait(false);
}
}
private static PredictResult PredictCore(List<PullCount> distribution, TypedWishSummary typedWishSummary)

View File

@@ -105,8 +105,8 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv
{
ReadOnlySpan<byte> span = stream.ToArray();
ReadOnlySpan<byte> match = isOversea
? "https://gs.hoyoverse.com/genshin/event/e20190909gacha-v2/index.html"u8
: "https://webstatic.mihoyo.com/hk4e/event/e20190909gacha-v2/index.html"u8;
? "https://gs.hoyoverse.com/genshin/event/e20190909gacha-v3/index.html"u8
: "https://webstatic.mihoyo.com/hk4e/event/e20190909gacha-v3/index.html"u8;
int index = span.LastIndexOf(match);
if (index >= 0)

View File

@@ -109,7 +109,7 @@ internal sealed class LaunchOptions : DbStoreOptions
public bool IsEnabled
{
get => GetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, true);
get => GetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, false);
set => SetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, value);
}

View File

@@ -17,9 +17,9 @@ internal sealed class LaunchStatus
public string Description { get; set; }
public static LaunchStatus FromUnlockStatus(UnlockerStatus unlockerStatus)
public static LaunchStatus FromUnlockerContext(GameFpsUnlockerContext unlockerState)
{
if (unlockerStatus.FindModuleState == FindModuleResult.Ok)
if (unlockerState.FindModuleResult == FindModuleResult.Ok)
{
return new(LaunchPhase.UnlockFpsSucceed, SH.ServiceGameLaunchPhaseUnlockFpsSucceed);
}

View File

@@ -9,7 +9,7 @@ internal sealed class LaunchExecutionBetterGenshinImpactAutomationHandlder : ILa
{
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
{
if (context.Options.UseBetterGenshinImpactAutomation)
if (!context.Process.HasExited && context.Options.UseBetterGenshinImpactAutomation)
{
context.Logger.LogInformation("Using BetterGI to automate gameplay");
await LaunchBetterGenshinImpactAsync(context).ConfigureAwait(false);
@@ -26,10 +26,12 @@ internal sealed class LaunchExecutionBetterGenshinImpactAutomationHandlder : ILa
try
{
context.Logger.LogInformation("Waiting game window to be ready");
context.Process.WaitForInputIdle();
SpinWait.SpinUntil(() => context.Process.MainWindowHandle != IntPtr.Zero);
}
catch (InvalidOperationException)
{
context.Logger.LogInformation("Failed to get game window handle");
return;
}

View File

@@ -1,18 +1,27 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Diagnostics;
namespace Snap.Hutao.Service.Game.Launching.Handler;
internal sealed class LaunchExecutionEnsureGameNotRunningHandler : ILaunchExecutionDelegateHandler
{
public static bool IsGameRunning([NotNullWhen(true)] out System.Diagnostics.Process? runningProcess)
public static bool IsGameRunning([NotNullWhen(true)] out Process? runningProcess)
{
int currentSessionId = Process.GetCurrentProcess().SessionId;
// GetProcesses once and manually loop is O(n)
foreach (ref readonly System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses().AsSpan())
foreach (ref readonly Process process in Process.GetProcesses().AsSpan())
{
if (string.Equals(process.ProcessName, GameConstants.YuanShenProcessName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(process.ProcessName, GameConstants.GenshinImpactProcessName, StringComparison.OrdinalIgnoreCase))
{
if (process.SessionId != currentSessionId)
{
continue;
}
runningProcess = process;
return true;
}
@@ -24,7 +33,7 @@ internal sealed class LaunchExecutionEnsureGameNotRunningHandler : ILaunchExecut
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
{
if (IsGameRunning(out System.Diagnostics.Process? process))
if (IsGameRunning(out Process? process))
{
context.Logger.LogInformation("Game process detected, id: {Id}", process.Id);

View File

@@ -9,7 +9,7 @@ internal sealed class LaunchExecutionStarwardPlayTimeStatisticsHandler : ILaunch
{
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
{
if (context.Options.UseStarwardPlayTimeStatistics)
if (!context.Process.HasExited && context.Options.UseStarwardPlayTimeStatistics)
{
context.Logger.LogInformation("Using Starward to count game time");
await LaunchStarwardForPlayTimeStatisticsAsync(context).ConfigureAwait(false);

View File

@@ -18,14 +18,17 @@ internal sealed class LaunchExecutionUnlockFpsHandler : ILaunchExecutionDelegate
context.Progress.Report(new(LaunchPhase.UnlockingFps, SH.ServiceGameLaunchPhaseUnlockingFps));
IProgressFactory progressFactory = context.ServiceProvider.GetRequiredService<IProgressFactory>();
IProgress<UnlockerStatus> progress = progressFactory.CreateForMainThread<UnlockerStatus>(status => context.Progress.Report(LaunchStatus.FromUnlockStatus(status)));
GameFpsUnlocker unlocker = context.ServiceProvider.CreateInstance<GameFpsUnlocker>(context.Process);
IProgress<GameFpsUnlockerContext> progress = progressFactory.CreateForMainThread<GameFpsUnlockerContext>(c => context.Progress.Report(LaunchStatus.FromUnlockerContext(c)));
GameFpsUnlocker unlocker = new(context.ServiceProvider, context.Process, new(100, 20000, 3000), progress);
try
{
await unlocker.UnlockAsync(new(100, 20000, 3000), progress, context.CancellationToken).ConfigureAwait(false);
if (await unlocker.UnlockAsync(context.CancellationToken).ConfigureAwait(false))
{
unlocker.PostUnlockAsync(context.CancellationToken).SafeForget();
}
}
catch (InvalidOperationException ex)
catch (Exception ex)
{
context.Logger.LogCritical(ex, "Unlocking FPS failed");

View File

@@ -24,25 +24,25 @@ internal sealed class LaunchExecutionInvoker
handlers.Enqueue(new LaunchExecutionGameProcessInitializationHandler());
handlers.Enqueue(new LaunchExecutionSetDiscordActivityHandler());
handlers.Enqueue(new LaunchExecutionGameProcessStartHandler());
handlers.Enqueue(new LaunchExecutionUnlockFpsHandler());
handlers.Enqueue(new LaunchExecutionStarwardPlayTimeStatisticsHandler());
handlers.Enqueue(new LaunchExecutionBetterGenshinImpactAutomationHandlder());
handlers.Enqueue(new LaunchExecutionUnlockFpsHandler());
handlers.Enqueue(new LaunchExecutionGameProcessExitHandler());
}
public async ValueTask<LaunchExecutionResult> InvokeAsync(LaunchExecutionContext context)
{
await InvokeHandlerAsync(context).ConfigureAwait(false);
await RecursiveInvokeHandlerAsync(context).ConfigureAwait(false);
return context.Result;
}
private async ValueTask<LaunchExecutionContext> InvokeHandlerAsync(LaunchExecutionContext context)
private async ValueTask<LaunchExecutionContext> RecursiveInvokeHandlerAsync(LaunchExecutionContext context)
{
if (handlers.TryDequeue(out ILaunchExecutionDelegateHandler? handler))
{
string typeName = TypeNameHelper.GetTypeDisplayName(handler, false);
context.Logger.LogInformation("Handler [{Handler}] begin execution", typeName);
await handler.OnExecutionAsync(context, () => InvokeHandlerAsync(context)).ConfigureAwait(false);
await handler.OnExecutionAsync(context, () => RecursiveInvokeHandlerAsync(context)).ConfigureAwait(false);
context.Logger.LogInformation("Handler [{Handler}] end execution", typeName);
}

View File

@@ -0,0 +1,82 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Memory;
using System.Diagnostics;
using static Snap.Hutao.Win32.Kernel32;
namespace Snap.Hutao.Service.Game.Unlocker;
internal static class GameFpsAddress
{
#pragma warning disable SA1310
private const byte ASM_CALL = 0xE8;
private const byte ASM_JMP = 0xE9;
#pragma warning restore SA1310
public static unsafe void UnsafeFindFpsAddress(GameFpsUnlockerContext state, in RequiredGameModule requiredGameModule)
{
bool readOk = UnsafeReadModulesMemory(state.GameProcess, requiredGameModule, out VirtualMemory localMemory);
HutaoException.ThrowIfNot(readOk, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed);
using (localMemory)
{
int offset = IndexOfPattern(localMemory.AsSpan()[(int)requiredGameModule.UnityPlayer.Size..]);
HutaoException.ThrowIfNot(offset >= 0, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerInterestedPatternNotFound);
byte* pLocalMemory = (byte*)localMemory.Pointer;
ref readonly Module unityPlayer = ref requiredGameModule.UnityPlayer;
ref readonly Module userAssembly = ref requiredGameModule.UserAssembly;
nuint localMemoryUnityPlayerAddress = (nuint)pLocalMemory;
nuint localMemoryUserAssemblyAddress = localMemoryUnityPlayerAddress + unityPlayer.Size;
nuint rip = localMemoryUserAssemblyAddress + (uint)offset;
rip += 5U;
rip += (nuint)(*(int*)(rip + 2U) + 6);
nuint address = userAssembly.Address + (rip - localMemoryUserAssemblyAddress);
nuint ptr = 0;
SpinWait.SpinUntil(() => UnsafeReadProcessMemory(state.GameProcess, address, out ptr) && ptr != 0);
rip = ptr - unityPlayer.Address + localMemoryUnityPlayerAddress;
while (*(byte*)rip is ASM_CALL or ASM_JMP)
{
rip += (nuint)(*(int*)(rip + 1) + 5);
}
nuint localMemoryActualAddress = rip + *(uint*)(rip + 2) + 6;
nuint actualOffset = localMemoryActualAddress - localMemoryUnityPlayerAddress;
state.FpsAddress = unityPlayer.Address + actualOffset;
}
}
private static int IndexOfPattern(in ReadOnlySpan<byte> memory)
{
// B9 3C 00 00 00 FF 15
ReadOnlySpan<byte> part = [0xB9, 0x3C, 0x00, 0x00, 0x00, 0xFF, 0x15];
return memory.IndexOf(part);
}
private static unsafe bool UnsafeReadModulesMemory(Process process, in RequiredGameModule moduleEntryInfo, out VirtualMemory memory)
{
ref readonly Module unityPlayer = ref moduleEntryInfo.UnityPlayer;
ref readonly Module userAssembly = ref moduleEntryInfo.UserAssembly;
memory = new VirtualMemory(unityPlayer.Size + userAssembly.Size);
return ReadProcessMemory(process.Handle, (void*)unityPlayer.Address, memory.AsSpan()[..(int)unityPlayer.Size], out _)
&& ReadProcessMemory(process.Handle, (void*)userAssembly.Address, memory.AsSpan()[(int)unityPlayer.Size..], out _);
}
private static unsafe bool UnsafeReadProcessMemory(Process process, nuint baseAddress, out nuint value)
{
value = 0;
bool result = ReadProcessMemory((HANDLE)process.Handle, (void*)baseAddress, ref value, out _);
HutaoException.ThrowIfNot(result, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerReadProcessMemoryPointerAddressFailed);
return result;
}
}

View File

@@ -1,11 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Memory;
using Snap.Hutao.Win32.System.ProcessStatus;
using System.Runtime.InteropServices;
using System.Diagnostics;
using static Snap.Hutao.Win32.Kernel32;
namespace Snap.Hutao.Service.Game.Unlocker;
@@ -17,255 +15,55 @@ namespace Snap.Hutao.Service.Game.Unlocker;
[HighQuality]
internal sealed class GameFpsUnlocker : IGameFpsUnlocker
{
private readonly System.Diagnostics.Process gameProcess;
private readonly LaunchOptions launchOptions;
private readonly UnlockerStatus status = new();
private readonly GameFpsUnlockerContext context = new();
/// <summary>
/// 构造一个新的 <see cref="GameFpsUnlocker"/> 对象,
/// 每个解锁器只能解锁一次原神的进程,
/// 再次解锁需要重新创建对象
/// <para/>
/// 解锁器需要在管理员模式下才能正确的完成解锁操作,
/// 非管理员模式不能解锁
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="gameProcess">游戏进程</param>
public GameFpsUnlocker(IServiceProvider serviceProvider, System.Diagnostics.Process gameProcess)
public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess, in UnlockTimingOptions options, IProgress<GameFpsUnlockerContext> progress)
{
launchOptions = serviceProvider.GetRequiredService<LaunchOptions>();
this.gameProcess = gameProcess;
context.GameProcess = gameProcess;
context.TimingOptions = options;
context.Progress = progress;
}
/// <inheritdoc/>
public async ValueTask UnlockAsync(UnlockTimingOptions options, IProgress<UnlockerStatus> progress, CancellationToken token = default)
public async ValueTask<bool> UnlockAsync(CancellationToken token = default)
{
Verify.Operation(status.IsUnlockerValid, "This Unlocker is invalid");
HutaoException.ThrowIfNot(context.IsUnlockerValid, HutaoExceptionKind.GameFpsUnlockingFailed, "This Unlocker is invalid");
(FindModuleResult result, RequiredGameModule gameModule) = await GameProcessModule.FindModuleAsync(context).ConfigureAwait(false);
HutaoException.ThrowIfNot(result != FindModuleResult.TimeLimitExeeded, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerFindModuleTimeLimitExeeded);
HutaoException.ThrowIfNot(result != FindModuleResult.NoModuleFound, HutaoExceptionKind.GameFpsUnlockingFailed, SH.ServiceGameUnlockerFindModuleNoModuleFound);
(FindModuleResult result, GameModule moduleEntryInfo) = await FindModuleAsync(options.FindModuleDelay, options.FindModuleLimit).ConfigureAwait(false);
Verify.Operation(result != FindModuleResult.TimeLimitExeeded, SH.ServiceGameUnlockerFindModuleTimeLimitExeeded);
Verify.Operation(result != FindModuleResult.NoModuleFound, SH.ServiceGameUnlockerFindModuleNoModuleFound);
// Read UnityPlayer.dll
UnsafeFindFpsAddress(moduleEntryInfo);
progress.Report(status);
// When player switch between scenes, we have to re adjust the fps
// So we keep a loop here
await LoopAdjustFpsAsync(options.AdjustFpsDelay, progress, token).ConfigureAwait(false);
GameFpsAddress.UnsafeFindFpsAddress(context, gameModule);
context.Report();
return context.FpsAddress != 0U;
}
private static unsafe bool UnsafeReadModulesMemory(System.Diagnostics.Process process, in GameModule moduleEntryInfo, out VirtualMemory memory)
public async ValueTask PostUnlockAsync(CancellationToken token = default)
{
ref readonly Module unityPlayer = ref moduleEntryInfo.UnityPlayer;
ref readonly Module userAssembly = ref moduleEntryInfo.UserAssembly;
memory = new VirtualMemory(unityPlayer.Size + userAssembly.Size);
return ReadProcessMemory(process.Handle, (void*)unityPlayer.Address, memory.AsSpan()[..(int)unityPlayer.Size], out _)
&& ReadProcessMemory(process.Handle, (void*)userAssembly.Address, memory.AsSpan()[(int)unityPlayer.Size..], out _);
}
private static unsafe bool UnsafeReadProcessMemory(System.Diagnostics.Process process, nuint baseAddress, out nuint value)
{
value = 0;
bool result = ReadProcessMemory((HANDLE)process.Handle, (void*)baseAddress, ref value, out _);
Verify.Operation(result, SH.ServiceGameUnlockerReadProcessMemoryPointerAddressFailed);
return result;
}
private static unsafe bool UnsafeWriteProcessMemory(System.Diagnostics.Process process, nuint baseAddress, int value)
{
return WriteProcessMemory((HANDLE)process.Handle, (void*)baseAddress, ref value, out _);
}
private static unsafe FindModuleResult UnsafeTryFindModule(in HANDLE hProcess, in ReadOnlySpan<char> moduleName, out Module module)
{
HMODULE[] buffer = new HMODULE[128];
if (!K32EnumProcessModules(hProcess, buffer, out uint actualSize))
{
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
}
if (actualSize == 0)
{
module = default!;
return FindModuleResult.NoModuleFound;
}
foreach (ref readonly HMODULE hModule in buffer.AsSpan()[..(int)(actualSize / sizeof(HMODULE))])
{
char[] baseName = new char[256];
if (K32GetModuleBaseNameW(hProcess, hModule, baseName) == 0)
{
continue;
}
fixed (char* lpBaseName = baseName)
{
ReadOnlySpan<char> szModuleName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(lpBaseName);
if (!szModuleName.SequenceEqual(moduleName))
{
continue;
}
}
if (!K32GetModuleInformation(hProcess, hModule, out MODULEINFO moduleInfo))
{
continue;
}
module = new((nuint)moduleInfo.lpBaseOfDll, moduleInfo.SizeOfImage);
return FindModuleResult.Ok;
}
module = default;
return FindModuleResult.ModuleNotLoaded;
}
private static int IndexOfPattern(in ReadOnlySpan<byte> memory)
{
// B9 3C 00 00 00 FF 15
ReadOnlySpan<byte> part = [0xB9, 0x3C, 0x00, 0x00, 0x00, 0xFF, 0x15];
return memory.IndexOf(part);
}
private static FindModuleResult UnsafeGetGameModuleInfo(in HANDLE hProcess, out GameModule info)
{
FindModuleResult unityPlayerResult = UnsafeTryFindModule(hProcess, "UnityPlayer.dll", out Module unityPlayer);
FindModuleResult userAssemblyResult = UnsafeTryFindModule(hProcess, "UserAssembly.dll", out Module userAssembly);
if (unityPlayerResult == FindModuleResult.Ok && userAssemblyResult == FindModuleResult.Ok)
{
info = new(unityPlayer, userAssembly);
return FindModuleResult.Ok;
}
if (unityPlayerResult == FindModuleResult.NoModuleFound && userAssemblyResult == FindModuleResult.NoModuleFound)
{
info = default;
return FindModuleResult.NoModuleFound;
}
info = default;
return FindModuleResult.ModuleNotLoaded;
}
private async ValueTask<ValueResult<FindModuleResult, GameModule>> FindModuleAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit)
{
ValueStopwatch watch = ValueStopwatch.StartNew();
using (PeriodicTimer timer = new(findModuleDelay))
{
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
{
FindModuleResult result = UnsafeGetGameModuleInfo((HANDLE)gameProcess.Handle, out GameModule gameModule);
if (result == FindModuleResult.Ok)
{
return new(FindModuleResult.Ok, gameModule);
}
if (result == FindModuleResult.NoModuleFound)
{
return new(FindModuleResult.NoModuleFound, default);
}
if (watch.GetElapsedTime() > findModuleLimit)
{
break;
}
}
}
return new(FindModuleResult.TimeLimitExeeded, default);
}
private async ValueTask LoopAdjustFpsAsync(TimeSpan adjustFpsDelay, IProgress<UnlockerStatus> progress, CancellationToken token)
{
using (PeriodicTimer timer = new(adjustFpsDelay))
using (PeriodicTimer timer = new(context.TimingOptions.AdjustFpsDelay))
{
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
{
if (!gameProcess.HasExited && status.FpsAddress != 0U)
if (!context.GameProcess.HasExited && context.FpsAddress != 0U)
{
UnsafeWriteProcessMemory(gameProcess, status.FpsAddress, launchOptions.TargetFps);
progress.Report(status);
UnsafeWriteProcessMemory(context.GameProcess, context.FpsAddress, launchOptions.TargetFps);
context.Report();
}
else
{
status.IsUnlockerValid = false;
status.FpsAddress = 0;
progress.Report(status);
context.IsUnlockerValid = false;
context.FpsAddress = 0;
context.Report();
return;
}
}
}
}
private unsafe void UnsafeFindFpsAddress(in GameModule moduleEntryInfo)
private static unsafe bool UnsafeWriteProcessMemory(Process process, nuint baseAddress, int value)
{
bool readOk = UnsafeReadModulesMemory(gameProcess, moduleEntryInfo, out VirtualMemory localMemory);
Verify.Operation(readOk, SH.ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed);
using (localMemory)
{
int offset = IndexOfPattern(localMemory.AsSpan()[(int)moduleEntryInfo.UnityPlayer.Size..]);
Must.Range(offset >= 0, SH.ServiceGameUnlockerInterestedPatternNotFound);
byte* pLocalMemory = (byte*)localMemory.Pointer;
ref readonly Module unityPlayer = ref moduleEntryInfo.UnityPlayer;
ref readonly Module userAssembly = ref moduleEntryInfo.UserAssembly;
nuint localMemoryUnityPlayerAddress = (nuint)pLocalMemory;
nuint localMemoryUserAssemblyAddress = localMemoryUnityPlayerAddress + unityPlayer.Size;
nuint rip = localMemoryUserAssemblyAddress + (uint)offset;
rip += 5U;
rip += (nuint)(*(int*)(rip + 2U) + 6);
nuint address = userAssembly.Address + (rip - localMemoryUserAssemblyAddress);
nuint ptr = 0;
SpinWait.SpinUntil(() => UnsafeReadProcessMemory(gameProcess, address, out ptr) && ptr != 0);
rip = ptr - unityPlayer.Address + localMemoryUnityPlayerAddress;
// CALL or JMP
while (*(byte*)rip == 0xE8 || *(byte*)rip == 0xE9)
{
rip += (nuint)(*(int*)(rip + 1) + 5);
}
nuint localMemoryActualAddress = rip + *(uint*)(rip + 2) + 6;
nuint actualOffset = localMemoryActualAddress - localMemoryUnityPlayerAddress;
status.FpsAddress = unityPlayer.Address + actualOffset;
}
}
private readonly struct GameModule
{
public readonly bool HasValue = false;
public readonly Module UnityPlayer;
public readonly Module UserAssembly;
public GameModule(in Module unityPlayer, in Module userAssembly)
{
HasValue = true;
UnityPlayer = unityPlayer;
UserAssembly = userAssembly;
}
}
private readonly struct Module
{
public readonly bool HasValue = false;
public readonly nuint Address;
public readonly uint Size;
public Module(nuint address, uint size)
{
HasValue = true;
Address = address;
Size = size;
}
return WriteProcessMemory((HANDLE)process.Handle, (void*)baseAddress, ref value, out _);
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Diagnostics;
namespace Snap.Hutao.Service.Game.Unlocker;
/// <summary>
/// 解锁状态
/// </summary>
internal sealed class GameFpsUnlockerContext
{
public string Description { get; set; } = default!;
public FindModuleResult FindModuleResult { get; set; }
public bool IsUnlockerValid { get; set; } = true;
public nuint FpsAddress { get; set; }
public UnlockTimingOptions TimingOptions { get; set; }
public Process GameProcess { get; set; } = default!;
public IProgress<GameFpsUnlockerContext> Progress { get; set; } = default!;
public void Report()
{
Progress.Report(this);
}
}

View File

@@ -0,0 +1,108 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Memory;
using Snap.Hutao.Win32.System.ProcessStatus;
using System.Diagnostics;
using System.Runtime.InteropServices;
using static Snap.Hutao.Win32.Kernel32;
namespace Snap.Hutao.Service.Game.Unlocker;
internal static class GameProcessModule
{
public static async ValueTask<ValueResult<FindModuleResult, RequiredGameModule>> FindModuleAsync(GameFpsUnlockerContext state)
{
ValueStopwatch watch = ValueStopwatch.StartNew();
using (PeriodicTimer timer = new(state.TimingOptions.FindModuleDelay))
{
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
{
FindModuleResult result = UnsafeGetGameModuleInfo((HANDLE)state.GameProcess.Handle, out RequiredGameModule gameModule);
if (result == FindModuleResult.Ok)
{
return new(FindModuleResult.Ok, gameModule);
}
if (result == FindModuleResult.NoModuleFound)
{
return new(FindModuleResult.NoModuleFound, default);
}
if (watch.GetElapsedTime() > state.TimingOptions.FindModuleLimit)
{
break;
}
}
}
return new(FindModuleResult.TimeLimitExeeded, default);
}
private static FindModuleResult UnsafeGetGameModuleInfo(in HANDLE hProcess, out RequiredGameModule info)
{
FindModuleResult unityPlayerResult = UnsafeFindModule(hProcess, "UnityPlayer.dll", out Module unityPlayer);
FindModuleResult userAssemblyResult = UnsafeFindModule(hProcess, "UserAssembly.dll", out Module userAssembly);
if (unityPlayerResult is FindModuleResult.Ok && userAssemblyResult is FindModuleResult.Ok)
{
info = new(unityPlayer, userAssembly);
return FindModuleResult.Ok;
}
if (unityPlayerResult is FindModuleResult.NoModuleFound && userAssemblyResult is FindModuleResult.NoModuleFound)
{
info = default;
return FindModuleResult.NoModuleFound;
}
info = default;
return FindModuleResult.ModuleNotLoaded;
}
private static unsafe FindModuleResult UnsafeFindModule(in HANDLE hProcess, in ReadOnlySpan<char> moduleName, out Module module)
{
HMODULE[] buffer = new HMODULE[128];
if (!K32EnumProcessModules(hProcess, buffer, out uint actualSize))
{
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
}
if (actualSize == 0)
{
module = default!;
return FindModuleResult.NoModuleFound;
}
foreach (ref readonly HMODULE hModule in buffer.AsSpan()[..(int)(actualSize / sizeof(HMODULE))])
{
char[] baseName = new char[256];
if (K32GetModuleBaseNameW(hProcess, hModule, baseName) == 0)
{
continue;
}
fixed (char* lpBaseName = baseName)
{
if (!moduleName.SequenceEqual(MemoryMarshal.CreateReadOnlySpanFromNullTerminated(lpBaseName)))
{
continue;
}
}
if (!K32GetModuleInformation(hProcess, hModule, out MODULEINFO moduleInfo))
{
continue;
}
module = new((nuint)moduleInfo.lpBaseOfDll, moduleInfo.SizeOfImage);
return FindModuleResult.Ok;
}
module = default;
return FindModuleResult.ModuleNotLoaded;
}
}

View File

@@ -9,12 +9,7 @@ namespace Snap.Hutao.Service.Game.Unlocker;
[HighQuality]
internal interface IGameFpsUnlocker
{
/// <summary>
/// 异步的解锁帧数限制
/// </summary>
/// <param name="options">选项</param>
/// <param name="progress">进度</param>
/// <param name="token">取消令牌</param>
/// <returns>解锁的结果</returns>
ValueTask UnlockAsync(UnlockTimingOptions options, IProgress<UnlockerStatus> progress, CancellationToken token = default);
ValueTask PostUnlockAsync(CancellationToken token = default);
ValueTask<bool> UnlockAsync(CancellationToken token = default);
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.Unlocker;
internal readonly struct Module
{
public readonly bool HasValue = false;
public readonly nuint Address;
public readonly uint Size;
public Module(nuint address, uint size)
{
HasValue = true;
Address = address;
Size = size;
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.Unlocker;
internal readonly struct RequiredGameModule
{
public readonly bool HasValue = false;
public readonly Module UnityPlayer;
public readonly Module UserAssembly;
public RequiredGameModule(in Module unityPlayer, in Module userAssembly)
{
HasValue = true;
UnityPlayer = unityPlayer;
UserAssembly = userAssembly;
}
}

View File

@@ -1,30 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.Unlocker;
/// <summary>
/// 解锁状态
/// </summary>
internal sealed class UnlockerStatus
{
/// <summary>
/// 状态描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 查找模块状态
/// </summary>
public FindModuleResult FindModuleState { get; set; }
/// <summary>
/// 当前解锁器是否有效
/// </summary>
public bool IsUnlockerValid { get; set; } = true;
/// <summary>
/// FPS 字节地址
/// </summary>
public nuint FpsAddress { get; set; }
}

View File

@@ -16,7 +16,7 @@ namespace Snap.Hutao.Service.Hutao;
[Injection(InjectAs.Scoped, typeof(IHutaoSpiralAbyssService))]
internal sealed partial class HutaoSpiralAbyssService : IHutaoSpiralAbyssService
{
private readonly TimeSpan cacheExpireTime = TimeSpan.FromHours(4);
private readonly TimeSpan cacheExpireTime = TimeSpan.FromHours(1);
private readonly IObjectCacheDbService objectCacheDbService;
private readonly HutaoSpiralAbyssClient homaClient;

View File

@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.View.Helper;
@@ -81,7 +82,7 @@ internal sealed class NavigationService : INavigationService, INavigationInitial
if (currentType == pageType)
{
logger.LogInformation("Navigate to {pageType} : succeed, already in", pageType);
logger.LogColorizedInformation("Navigate to {Page} : {Result}, already in", (pageType, ConsoleColor.DarkGreen), ("succeed", ConsoleColor.Green));
return NavigationResult.AlreadyNavigatedTo;
}
@@ -91,7 +92,7 @@ internal sealed class NavigationService : INavigationService, INavigationInitial
try
{
navigated = frame?.Navigate(pageType, data) ?? false;
logger.LogInformation("Navigate to {pageType} : {result}", pageType, navigated ? "succeed" : "failed");
logger.LogColorizedInformation("Navigate to {Page} : {Result}", (pageType, ConsoleColor.Magenta), navigated ? ("succeed", ConsoleColor.Green) : ("succeed", ConsoleColor.Red));
}
catch (Exception ex)
{

View File

@@ -302,8 +302,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -311,14 +311,14 @@
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.1" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.2.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240311000" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.15.3">

View File

@@ -15,6 +15,7 @@
HorizontalContentAlignment="Stretch"
d:DataContext="{d:DesignInstance shva:AchievementViewModelSlim}"
Background="Transparent"
BorderBrush="{x:Null}"
Command="{Binding NavigateCommand}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">

View File

@@ -17,6 +17,7 @@
HorizontalContentAlignment="Stretch"
d:DataContext="{d:DesignInstance shvd:DailyNoteViewModelSlim}"
Background="Transparent"
BorderBrush="{x:Null}"
Command="{Binding NavigateCommand}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">

View File

@@ -16,6 +16,7 @@
HorizontalContentAlignment="Stretch"
d:DataContext="{d:DesignInstance shvg:GachaLogViewModelSlim}"
Background="Transparent"
BorderBrush="{x:Null}"
Command="{Binding NavigateCommand}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">
@@ -69,7 +70,7 @@
Maximum="{Binding GuaranteeOrangeThreshold}"
ProgressForeground="{StaticResource OrangeColorBrush}"
TextForeground="{StaticResource OrangeColorBrush}"
Value="{Binding LastOrangePull}"/>
Value="{Binding LastOrangePull, Mode=OneWay}"/>
<shvcp:CardProgressBar
Grid.Column="0"
Description="{Binding LastPurplePull}"

View File

@@ -17,6 +17,7 @@
VerticalContentAlignment="Stretch"
d:DataContext="{d:DesignInstance shvg:LaunchGameViewModelSlim}"
Background="Transparent"
BorderBrush="{x:Null}"
Command="{Binding LaunchCommand}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">

View File

@@ -13,12 +13,9 @@ namespace Snap.Hutao.View.Card.Primitive;
[DependencyProperty("Value", typeof(double))]
[DependencyProperty("Header", typeof(string))]
[DependencyProperty("Description", typeof(string))]
internal sealed partial class CardProgressBar : Grid
internal sealed partial class CardProgressBar : ContentControl
{
public CardProgressBar()
{
IAppResourceProvider appResourceProvider = Ioc.Default.GetRequiredService<IAppResourceProvider>();
TextForeground = appResourceProvider.GetResource<Brush>("TextFillColorPrimaryBrush");
InitializeComponent();
}
}

View File

@@ -1,39 +1,49 @@
<Grid
x:Class="Snap.Hutao.View.Card.Primitive.CardProgressBar"
<ResourceDictionary
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"
MinHeight="40"
Style="{ThemeResource GridCardStyle}"
mc:Ignorable="d">
xmlns:shvcp="using:Snap.Hutao.View.Card.Primitive">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressBar
Grid.ColumnSpan="2"
MinHeight="{x:Bind MinHeight, Mode=OneWay}"
Background="Transparent"
CornerRadius="{StaticResource ControlCornerRadius}"
Foreground="{x:Bind ProgressForeground, Mode=OneWay}"
Maximum="{x:Bind Maximum, Mode=OneWay}"
Opacity="{StaticResource LargeBackgroundProgressBarOpacity}"
Value="{x:Bind Value, Mode=OneWay}"/>
<TextBlock
Grid.Column="0"
Margin="6,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{x:Bind TextForeground, Mode=OneWay}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Header, Mode=OneWay}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{x:Bind TextForeground, Mode=OneWay}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Description, Mode=OneWay}"/>
</Grid>
<Style BasedOn="{StaticResource DefaultCardProgressBarStyle}" TargetType="shvcp:CardProgressBar"/>
<Style x:Key="DefaultCardProgressBarStyle" TargetType="shvcp:CardProgressBar">
<Setter Property="TextForeground" Value="{ThemeResource TextFillColorPrimaryBrush}"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid MinHeight="{TemplateBinding MinHeight}" Style="{ThemeResource GridCardStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressBar
Grid.ColumnSpan="2"
MinHeight="{TemplateBinding MinHeight}"
Background="Transparent"
CornerRadius="{StaticResource ControlCornerRadius}"
Foreground="{Binding ProgressForeground, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{Binding Maximum, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
Opacity="{StaticResource LargeBackgroundProgressBarOpacity}"
Value="{Binding Value, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>
<TextBlock
Grid.Column="0"
Margin="6,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{Binding TextForeground, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Header, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{Binding TextForeground, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Description, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -52,41 +52,49 @@
Margin="0,16,0,8"
Foreground="{ThemeResource UpPullColorBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardUpText}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardUpText}"
Visibility="{Binding UpItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<ItemsControl
ItemTemplate="{StaticResource GridTemplate}"
ItemsPanel="{StaticResource WrapPanelSpacing4Template}"
ItemsSource="{Binding UpItems}"/>
ItemsSource="{Binding UpItems}"
Visibility="{Binding UpItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<TextBlock
Margin="0,16,0,8"
Foreground="{ThemeResource OrangeColorBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"
Visibility="{Binding OrangeItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<ItemsControl
ItemTemplate="{StaticResource GridTemplate}"
ItemsPanel="{StaticResource WrapPanelSpacing4Template}"
ItemsSource="{Binding OrangeItems}"/>
ItemsSource="{Binding OrangeItems}"
Visibility="{Binding OrangeItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<TextBlock
Margin="0,16,0,8"
Foreground="{ThemeResource PurpleColorBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"
Visibility="{Binding PurpleItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<ItemsControl
ItemTemplate="{StaticResource GridTemplate}"
ItemsPanel="{StaticResource WrapPanelSpacing4Template}"
ItemsSource="{Binding PurpleItems}"/>
ItemsSource="{Binding PurpleItems}"
Visibility="{Binding PurpleItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<TextBlock
Margin="0,16,0,8"
Foreground="{ThemeResource BlueColorBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardBlueText}"/>
Text="{shcm:ResourceString Name=ViewControlStatisticsCardBlueText}"
Visibility="{Binding BlueItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
<ItemsControl
ItemTemplate="{StaticResource GridTemplate}"
ItemsPanel="{StaticResource WrapPanelSpacing4Template}"
ItemsSource="{Binding BlueItems}"/>
ItemsSource="{Binding BlueItems}"
Visibility="{Binding BlueItems.Count, Converter={StaticResource Int32ToVisibilityConverter}}"/>
</StackPanel>
</ScrollViewer>
</Grid>

View File

@@ -21,6 +21,12 @@
mc:Ignorable="d">
<UserControl.Resources>
<shvconv:BoolToGridLengthConverter x:Key="BoolToGridLengthConverter"/>
<shvconv:BoolToGridLengthConverter
x:Key="BoolToGridLengthSpacingConverter"
FalseValue="0"
TrueValue="4"/>
<shvconv:Int32ToGradientColorConverter x:Key="Int32ToGradientColorConverter" MaximumValue="{Binding GuaranteeOrangeThreshold}"/>
<DataTemplate x:Key="OrangeListTemplate" d:DataType="shvg:SummaryItem">
@@ -226,10 +232,12 @@
Padding="0,12"
Value="{x:Bind StatisticsSegmented.SelectedIndex, Mode=OneWay}">
<cwcont:Case Value="{shcm:Int32 Value=0}">
<Grid ColumnSpacing="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition Width="{x:Bind ShowUpPull, Converter={StaticResource BoolToGridLengthSpacingConverter}}"/>
<ColumnDefinition Width="{x:Bind ShowUpPull, Converter={StaticResource BoolToGridLengthConverter}}"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Style="{ThemeResource BorderCardStyle}">
@@ -243,7 +251,7 @@
</StackPanel>
</Viewbox>
</Border>
<Border Grid.Column="1" Style="{ThemeResource BorderCardStyle}">
<Border Grid.Column="2" Style="{ThemeResource BorderCardStyle}">
<Viewbox Margin="8,0" StretchDirection="DownOnly">
<Grid>
<StackPanel
@@ -266,7 +274,7 @@
</Viewbox>
</Border>
<Grid Grid.Column="2" RowSpacing="2">
<Grid Grid.Column="4" RowSpacing="4">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
@@ -298,7 +306,7 @@
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid ColumnSpacing="2">
<Grid ColumnSpacing="4">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
@@ -360,7 +368,7 @@
</cwcont:Case>
<cwcont:Case Value="{shcm:Int32 Value=2}">
<Grid RowSpacing="2">
<Grid RowSpacing="4">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Converters;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.View.Converter;
internal sealed class BoolToGridLengthConverter : BoolToObjectConverter
{
public BoolToGridLengthConverter()
{
TrueValue = new GridLength(1D, GridUnitType.Star);
FalseValue = new GridLength(0D);
}
}

View File

@@ -212,7 +212,7 @@
LocalSettingKeySuffixForCurrent="AchievementPage.AchievementGoals"/>
<Viewbox
Height="32"
MaxWidth="192"
MaxWidth="190"
Margin="8,0,0,0"
HorizontalAlignment="Left"
Stretch="Uniform"

View File

@@ -43,6 +43,7 @@
cw:VisualExtensions.NormalizedCenterPoint="0.5">
<cww:ConstrainedBox AspectRatio="1080:390" CornerRadius="{ThemeResource ControlCornerRadiusTop}">
<shci:CachedImage
Margin="-1"
VerticalAlignment="Center"
PlaceholderMargin="16"
PlaceholderSource="{StaticResource UI_EmotionIcon271}"

View File

@@ -174,12 +174,20 @@
</ItemContainer.Resources>
<shvcp:HorizontalCard>
<shvcp:HorizontalCard.Left>
<shvco:ItemIcon
Grid.Column="0"
Width="40"
Height="40"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<Grid Grid.Column="0">
<shvco:ItemIcon
Width="40"
Height="40"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<FontIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Glyph="&#xE73E;"
Visibility="{Binding IsFinished, Converter={StaticResource BoolToVisibilityConverter}}"/>
</Grid>
</shvcp:HorizontalCard.Left>
<shvcp:HorizontalCard.Right>
<Grid Margin="16,0">

View File

@@ -3,6 +3,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:clw="using:CommunityToolkit.Labs.WinUI"
xmlns:cw="using:CommunityToolkit.WinUI"
xmlns:cwcont="using:CommunityToolkit.WinUI.Controls"
xmlns:cwconv="using:CommunityToolkit.WinUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -50,240 +51,248 @@
<SplitView.Pane>
<ScrollViewer>
<StackPanel Margin="16" Spacing="3">
<Border Style="{ThemeResource AcrylicBorderCardStyle}">
<cwcont:SettingsExpander
Description="{Binding RuntimeOptions.Version}"
Header="{shcm:ResourceString Name=AppName}"
HeaderIcon="{shcm:FontIcon Glyph=&#xECAA;}"
IsExpanded="True">
<cwcont:SettingsExpander.Items>
<cwcont:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE8C8;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCopyDeviceIdAction}"
Command="{Binding CopyDeviceIdCommand}"
Description="{Binding RuntimeOptions.DeviceId}"
Header="{shcm:ResourceString Name=ViewPageSettingDeviceIdHeader}"
IsClickEnabled="True"/>
<cwcont:SettingsCard Description="{Binding IPInformation}" Header="{shcm:ResourceString Name=ViewPageSettingDeviceIpHeader}"/>
<cwcont:SettingsCard Description="{Binding DynamicHttpProxy.CurrentProxyUri}" Header="{shcm:ResourceString Name=ViewPageFeedbackCurrentProxyHeader}"/>
<cwcont:SettingsCard
Command="{Binding EnableLoopbackCommand}"
Header="{shcm:ResourceString Name=ViewPageFeedbackEnableLoopbackHeader}"
IsClickEnabled="True"
IsEnabled="{Binding LoopbackManager.IsLoopbackEnabled, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<cwcont:SettingsCard.Description>
<UserControl Content="{Binding LoopbackManager.IsLoopbackEnabled, Converter={StaticResource BoolToLoopbackDescriptionControlConverter}, Mode=OneWay}"/>
</cwcont:SettingsCard.Description>
</cwcont:SettingsCard>
<cwcont:SettingsCard Description="{Binding RuntimeOptions.WebView2Version}" Header="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
</cwcont:SettingsExpander.Items>
</cwcont:SettingsExpander>
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Border Style="{ThemeResource AcrylicBorderCardStyle}">
<cwcont:SettingsExpander
Description="{Binding RuntimeOptions.Version}"
Header="{shcm:ResourceString Name=AppName}"
HeaderIcon="{shcm:FontIcon Glyph=&#xECAA;}"
IsExpanded="True">
<cwcont:SettingsExpander.Items>
<cwcont:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE8C8;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCopyDeviceIdAction}"
Command="{Binding CopyDeviceIdCommand}"
Description="{Binding RuntimeOptions.DeviceId}"
Header="{shcm:ResourceString Name=ViewPageSettingDeviceIdHeader}"
IsClickEnabled="True"/>
<cwcont:SettingsCard Description="{Binding IPInformation}" Header="{shcm:ResourceString Name=ViewPageSettingDeviceIpHeader}"/>
<cwcont:SettingsCard Description="{Binding DynamicHttpProxy.CurrentProxyUri}" Header="{shcm:ResourceString Name=ViewPageFeedbackCurrentProxyHeader}"/>
<cwcont:SettingsCard
Command="{Binding EnableLoopbackCommand}"
Header="{shcm:ResourceString Name=ViewPageFeedbackEnableLoopbackHeader}"
IsClickEnabled="True"
IsEnabled="{Binding LoopbackManager.IsLoopbackEnabled, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<cwcont:SettingsCard.Description>
<UserControl Content="{Binding LoopbackManager.IsLoopbackEnabled, Converter={StaticResource BoolToLoopbackDescriptionControlConverter}, Mode=OneWay}"/>
</cwcont:SettingsCard.Description>
</cwcont:SettingsCard>
<cwcont:SettingsCard Description="{Binding RuntimeOptions.WebView2Version}" Header="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
</cwcont:SettingsExpander.Items>
</cwcont:SettingsExpander>
</Border>
</Border>
<Border Style="{ThemeResource AcrylicBorderCardStyle}">
<cwcont:SettingsExpander
Description="{shcm:ResourceString Name=ViewPageFeedbackEngageWithUsDescription}"
Header="{shcm:ResourceString Name=ViewPageFeedbackCommonLinksHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE71B;}"
IsExpanded="True">
<cwcont:SettingsExpander.Items>
<cwcont:SettingsCard
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://github.com/DGP-Studio/Snap.Hutao/issues/new/choose"
Description="{shcm:ResourceString Name=ViewPageFeedbackGithubIssuesDescription}"
Header="GitHub Issues"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://github.com/orgs/DGP-Studio/projects/2"
Description="{shcm:ResourceString Name=ViewPageFeedbackRoadmapDescription}"
Header="GitHub Projects"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://status.hut.ao"
Description="{shcm:ResourceString Name=ViewPageFeedbackServerStatusDescription}"
Header="{shcm:ResourceString Name=ViewPageFeedbackServerStatusHeader}"
IsClickEnabled="True"/>
</cwcont:SettingsExpander.Items>
</cwcont:SettingsExpander>
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Border Style="{ThemeResource AcrylicBorderCardStyle}">
<cwcont:SettingsExpander
Description="{shcm:ResourceString Name=ViewPageFeedbackEngageWithUsDescription}"
Header="{shcm:ResourceString Name=ViewPageFeedbackCommonLinksHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE71B;}"
IsExpanded="True">
<cwcont:SettingsExpander.Items>
<cwcont:SettingsCard
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://github.com/DGP-Studio/Snap.Hutao/issues/new/choose"
Description="{shcm:ResourceString Name=ViewPageFeedbackGithubIssuesDescription}"
Header="GitHub Issues"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://github.com/orgs/DGP-Studio/projects/2"
Description="{shcm:ResourceString Name=ViewPageFeedbackRoadmapDescription}"
Header="GitHub Projects"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://status.snapgenshin.cn/status"
Description="{shcm:ResourceString Name=ViewPageFeedbackServerStatusDescription}"
Header="{shcm:ResourceString Name=ViewPageFeedbackServerStatusHeader}"
IsClickEnabled="True"/>
</cwcont:SettingsExpander.Items>
</cwcont:SettingsExpander>
</Border>
</Border>
<Border Style="{ThemeResource AcrylicBorderCardStyle}">
<cwcont:SettingsExpander
Header="{shcm:ResourceString Name=ViewPageFeedbackFeatureGuideHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xF8A5;}"
IsExpanded="True">
<cwcont:SettingsExpander.Items>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/dashboard.html"
Header="{shcm:ResourceString Name=ViewAnnouncementHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/Announcement.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/game-launcher.html"
Header="{shcm:ResourceString Name=ViewLaunchGameHeader}"
IsClickEnabled="True">
<cwcont:SettingsCard.HeaderIcon>
<!-- This icon is not a square -->
<BitmapIcon
Width="24"
Height="24"
ShowAsMonochrome="False"
UriSource="ms-appx:///Resource/Navigation/LaunchGame.png"/>
</cwcont:SettingsCard.HeaderIcon>
</cwcont:SettingsCard>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/wish-export.html"
Header="{shcm:ResourceString Name=ViewGachaLogHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/GachaLog.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/achievements.html"
Header="{shcm:ResourceString Name=ViewAchievementHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/Achievement.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/real-time-notes.html"
Header="{shcm:ResourceString Name=ViewDailyNoteHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/DailyNote.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/character-data.html"
Header="{shcm:ResourceString Name=ViewAvatarPropertyHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/AvatarProperty.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/hutao-API.html"
Header="{shcm:ResourceString Name=ViewSpiralAbyssHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/SpiralAbyss.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/develop-plan.html"
Header="{shcm:ResourceString Name=ViewCultivationHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/Cultivation.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/character-wiki.html"
Header="{shcm:ResourceString Name=ViewWikiAvatarHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/WikiAvatar.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/weapon-wiki.html"
Header="{shcm:ResourceString Name=ViewWikiWeaponHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/WikiWeapon.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/monster-wiki.html"
Header="{shcm:ResourceString Name=ViewWikiMonsterHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/WikiMonster.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/hutao-settings.html"
Header="{shcm:ResourceString Name=ViewSettingHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE713;}"
IsClickEnabled="True"/>
</cwcont:SettingsExpander.Items>
</cwcont:SettingsExpander>
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Border Style="{ThemeResource AcrylicBorderCardStyle}">
<cwcont:SettingsExpander
Header="{shcm:ResourceString Name=ViewPageFeedbackFeatureGuideHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xF8A5;}"
IsExpanded="True">
<cwcont:SettingsExpander.Items>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/dashboard.html"
Header="{shcm:ResourceString Name=ViewAnnouncementHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/Announcement.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/game-launcher.html"
Header="{shcm:ResourceString Name=ViewLaunchGameHeader}"
IsClickEnabled="True">
<cwcont:SettingsCard.HeaderIcon>
<!-- This icon is not a square -->
<BitmapIcon
Width="24"
Height="24"
ShowAsMonochrome="False"
UriSource="ms-appx:///Resource/Navigation/LaunchGame.png"/>
</cwcont:SettingsCard.HeaderIcon>
</cwcont:SettingsCard>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/wish-export.html"
Header="{shcm:ResourceString Name=ViewGachaLogHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/GachaLog.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/achievements.html"
Header="{shcm:ResourceString Name=ViewAchievementHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/Achievement.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/real-time-notes.html"
Header="{shcm:ResourceString Name=ViewDailyNoteHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/DailyNote.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/character-data.html"
Header="{shcm:ResourceString Name=ViewAvatarPropertyHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/AvatarProperty.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/hutao-API.html"
Header="{shcm:ResourceString Name=ViewSpiralAbyssHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/SpiralAbyss.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/develop-plan.html"
Header="{shcm:ResourceString Name=ViewCultivationHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/Cultivation.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/character-wiki.html"
Header="{shcm:ResourceString Name=ViewWikiAvatarHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/WikiAvatar.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/weapon-wiki.html"
Header="{shcm:ResourceString Name=ViewWikiWeaponHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/WikiWeapon.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/monster-wiki.html"
Header="{shcm:ResourceString Name=ViewWikiMonsterHeader}"
HeaderIcon="{shcm:BitmapIcon Source=ms-appx:///Resource/Navigation/WikiMonster.png}"
IsClickEnabled="True"/>
<cwcont:SettingsCard
Padding="{ThemeResource SettingsExpanderItemHasIconPadding}"
Command="{Binding NavigateToUriCommand}"
CommandParameter="https://hut.ao/features/hutao-settings.html"
Header="{shcm:ResourceString Name=ViewSettingHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE713;}"
IsClickEnabled="True"/>
</cwcont:SettingsExpander.Items>
</cwcont:SettingsExpander>
</Border>
</Border>
</StackPanel>
</ScrollViewer>
</SplitView.Pane>
<Grid Margin="16,16,0,16" Style="{ThemeResource AcrylicGridCardStyle}">
<Grid
Padding="16"
RowSpacing="8"
Visibility="{Binding IsInitialized, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<AutoSuggestBox
Grid.Row="0"
Height="36"
Margin="0,0,0,8"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center"
PlaceholderText="{shcm:ResourceString Name=ViewPageFeedbackAutoSuggestBoxPlaceholder}"
QueryIcon="{shcm:FontIcon Glyph=&#xE721;}"
Style="{StaticResource DefaultAutoSuggestBoxStyle}"
Text="{Binding SearchText, Mode=TwoWay}">
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="QuerySubmitted">
<mxic:InvokeCommandAction Command="{Binding SearchDocumentCommand}" CommandParameter="{Binding SearchText}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</AutoSuggestBox>
<StackPanel
Grid.Row="1"
VerticalAlignment="Center"
Visibility="{Binding SearchResults.Count, Converter={StaticResource Int32ToVisibilityRevertConverter}}">
<shci:CachedImage
Height="120"
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
EnableLazyLoading="False"
Source="{StaticResource UI_EmotionIcon52}"/>
<TextBlock
Margin="0,5,0,21"
HorizontalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageFeedbackSearchResultPlaceholderTitle}"/>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Hidden">
<ItemsControl
ItemContainerTransitions="{ThemeResource ListViewLikeThemeTransitions}"
ItemsPanel="{ThemeResource StackPanelSpacing8Template}"
ItemsSource="{Binding SearchResults}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Style="{ThemeResource BorderCardStyle}">
<HyperlinkButton
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
NavigateUri="{Binding Url}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<BreadcrumbBar
Grid.Column="0"
Margin="4,8,8,4"
IsHitTestVisible="False"
ItemsSource="{Binding Hierarchy.DisplayLevels}"/>
</Grid>
</HyperlinkButton>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<Border Margin="16,16,0,16" cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Grid Style="{ThemeResource AcrylicGridCardStyle}">
<Grid
Padding="16"
RowSpacing="8"
Visibility="{Binding IsInitialized, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<AutoSuggestBox
Grid.Row="0"
Height="36"
Margin="0,0,0,8"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center"
PlaceholderText="{shcm:ResourceString Name=ViewPageFeedbackAutoSuggestBoxPlaceholder}"
QueryIcon="{shcm:FontIcon Glyph=&#xE721;}"
Style="{StaticResource DefaultAutoSuggestBoxStyle}"
Text="{Binding SearchText, Mode=TwoWay}">
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="QuerySubmitted">
<mxic:InvokeCommandAction Command="{Binding SearchDocumentCommand}" CommandParameter="{Binding SearchText}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</AutoSuggestBox>
<StackPanel
Grid.Row="1"
VerticalAlignment="Center"
Visibility="{Binding SearchResults.Count, Converter={StaticResource Int32ToVisibilityRevertConverter}}">
<shci:CachedImage
Height="120"
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
EnableLazyLoading="False"
Source="{StaticResource UI_EmotionIcon52}"/>
<TextBlock
Margin="0,5,0,21"
HorizontalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageFeedbackSearchResultPlaceholderTitle}"/>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Hidden">
<ItemsControl
ItemContainerTransitions="{ThemeResource ListViewLikeThemeTransitions}"
ItemsPanel="{ThemeResource StackPanelSpacing8Template}"
ItemsSource="{Binding SearchResults}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Style="{ThemeResource BorderCardStyle}">
<HyperlinkButton
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
NavigateUri="{Binding Url}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<BreadcrumbBar
Grid.Column="0"
Margin="4,8,8,4"
IsHitTestVisible="False"
ItemsSource="{Binding Hierarchy.DisplayLevels}"/>
</Grid>
</HyperlinkButton>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<clw:Shimmer IsActive="{Binding IsInitialized, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" Visibility="{Binding IsInitialized, Converter={StaticResource BoolToVisibilityRevertConverter}, Mode=OneWay}"/>
</Grid>
<clw:Shimmer IsActive="{Binding IsInitialized, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" Visibility="{Binding IsInitialized, Converter={StaticResource BoolToVisibilityRevertConverter}, Mode=OneWay}"/>
</Grid>
</Border>
</SplitView>
</Grid>
</shc:ScopedPage>

View File

@@ -12,6 +12,7 @@
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shcp="using:Snap.Hutao.Control.Panel"
xmlns:shvc="using:Snap.Hutao.View.Control"
xmlns:shvg="using:Snap.Hutao.ViewModel.GachaLog"
d:DataContext="{d:DesignInstance shvg:GachaLogViewModel}"
@@ -142,8 +143,8 @@
<Border Width="40" Style="{StaticResource BorderCardStyle}">
<StackPanel>
<shvc:ItemIcon
Width="40"
Height="40"
Width="38"
Height="38"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
<TextBlock
@@ -182,17 +183,19 @@
Text="{Binding TotalCountFormatted}"/>
<ItemsControl
Grid.Row="1"
MaxWidth="124"
Margin="0,6,0,0"
HorizontalAlignment="Left"
ItemTemplate="{StaticResource HistoryWishItemTemplate}"
ItemsPanel="{StaticResource HorizontalStackPanelSpacing2Template}"
ItemsPanel="{StaticResource WrapPanelSpacing2Template}"
ItemsSource="{Binding OrangeUpList}"/>
<ItemsControl
Grid.Row="1"
MaxWidth="252"
Margin="0,6,0,0"
HorizontalAlignment="Right"
ItemTemplate="{StaticResource HistoryWishItemTemplate}"
ItemsPanel="{StaticResource HorizontalStackPanelSpacing2Template}"
ItemsPanel="{StaticResource WrapPanelSpacing2Template}"
ItemsSource="{Binding PurpleUpList}"/>
<TextBlock
Grid.Row="2"
@@ -285,26 +288,22 @@
</CommandBar>
</Pivot.RightHeader>
<PivotItem Header="{shcm:ResourceString Name=ViewPageGahcaLogPivotOverview}">
<Grid Margin="0,0,16,0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:StatisticsCard
Grid.Column="0"
Margin="16,16,0,16"
DataContext="{Binding Statistics.AvatarWish}"/>
<shvc:StatisticsCard
Grid.Column="1"
Margin="16,16,0,16"
DataContext="{Binding Statistics.WeaponWish}"/>
<shvc:StatisticsCard
Grid.Column="2"
Margin="16,16,0,16"
DataContext="{Binding Statistics.StandardWish}"
ShowUpPull="False"/>
</Grid>
<ScrollViewer
Margin="16,0"
cw:ScrollViewerExtensions.HorizontalScrollBarMargin="0,0,-16,0"
HorizontalScrollBarVisibility="Auto"
HorizontalScrollMode="Enabled"
VerticalScrollMode="Disabled">
<shcp:HorizontalEqualPanel
Margin="0,16"
MinItemWidth="302"
Spacing="16">
<shvc:StatisticsCard DataContext="{Binding Statistics.AvatarWish}"/>
<shvc:StatisticsCard DataContext="{Binding Statistics.WeaponWish}"/>
<shvc:StatisticsCard DataContext="{Binding Statistics.StandardWish}" ShowUpPull="False"/>
<shvc:StatisticsCard DataContext="{Binding Statistics.ChronicledWish}" ShowUpPull="False"/>
</shcp:HorizontalEqualPanel>
</ScrollViewer>
</PivotItem>
<PivotItem Header="{shcm:ResourceString Name=ViewPageGahcaLogPivotHistory}">
<Border Margin="16" cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
@@ -312,7 +311,7 @@
<SplitView
DisplayMode="Inline"
IsPaneOpen="True"
OpenPaneLength="323"
OpenPaneLength="408"
PaneBackground="{ThemeResource CardBackgroundFillColorSecondaryBrush}">
<SplitView.Pane>
<ListView
@@ -379,7 +378,6 @@
</SplitView>
</Border>
</Border>
</PivotItem>
<PivotItem Header="{shcm:ResourceString Name=ViewPageGahcaLogPivotAvatar}">
<ScrollViewer>
@@ -481,13 +479,14 @@
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:HutaoStatisticsCard Grid.Column="0" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent}"/>
<shvc:HutaoStatisticsCard Grid.Column="1" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.AvatarEvent2}"/>
<shvc:HutaoStatisticsCard Grid.Column="2" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.WeaponEvent}"/>
<shvc:HutaoStatisticsCard Grid.Column="3" DataContext="{Binding HutaoCloudStatisticsViewModel.Statistics.Chronicled}"/>
</Grid>
</Grid>
</PivotItem>
</Pivot>
</Grid>

View File

@@ -348,6 +348,7 @@
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</cwc:SettingsCard>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewPageSettingBackgroundImageLocalFolderCopyrightHeader}" Visibility="{Binding BackgroundImageOptions.Wallpaper, Converter={StaticResource EmptyObjectToBoolRevertConverter}}"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
</StackPanel>

View File

@@ -725,7 +725,7 @@
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<shcp:UniformPanel
Padding="0,0,16,0"
Margin="0,0,16,0"
ColumnSpacing="6"
MinItemWidth="240"
RowSpacing="2"/>
@@ -752,7 +752,7 @@
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<shcp:UniformPanel
Padding="0,0,16,0"
Margin="0,0,16,0"
ColumnSpacing="6"
MinItemWidth="240"
RowSpacing="2"/>

View File

@@ -74,30 +74,28 @@
</DataTemplate>
<DataTemplate x:Key="TokenTemplate">
<Grid>
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="22"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle
<Ellipse
Grid.Column="0"
Width="4"
Height="22"
Margin="0,0,8,0"
Width="12"
Height="12"
Margin="1,1,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
RadiusX="2"
RadiusY="2"
Visibility="{Binding Quality, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Rectangle.Fill>
<Ellipse.Fill>
<SolidColorBrush Color="{Binding Quality}"/>
</Rectangle.Fill>
</Rectangle>
</Ellipse.Fill>
</Ellipse>
<shci:MonoChrome
Grid.Column="0"
Width="22"
Height="22"
Margin="0,0,4,0"
Margin="-3,2,7,0"
Source="{Binding IconUri, Mode=OneWay}"
Visibility="{Binding IconUri, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
<shci:CachedImage
@@ -367,367 +365,378 @@
</SplitView.Pane>
<SplitView.Content>
<ScrollViewer>
<StackPanel
Padding="16,16,16,16"
HorizontalAlignment="Left"
Spacing="16">
<!-- 简介 -->
<Grid 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>
<shvcont: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 Style="{ThemeResource BorderCardStyle}">
<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>
<shvcont:BaseValueSlider
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
BaseValueInfo="{Binding BaseValueInfo, Mode=OneWay}"/>
</Border>
<!-- 天赋 -->
<Border Padding="16" Style="{ThemeResource BorderCardStyle}">
<shvcont:SkillPivot ItemTemplate="{StaticResource SkillDataTemplate}" Skills="{Binding Selected.SkillDepot.CompositeSkills}"/>
</Border>
<!-- 命座 -->
<Border Padding="16" Style="{ThemeResource BorderCardStyle}">
<shvcont:SkillPivot ItemTemplate="{StaticResource TalentDataTemplate}" Skills="{Binding Selected.SkillDepot.Talents}"/>
</Border>
<!-- 搭配 -->
<Border Padding="16" Style="{ThemeResource BorderCardStyle}">
<Grid ColumnSpacing="8" RowSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Padding="16">
<StackPanel VerticalAlignment="Center" Visibility="{Binding Avatars.Count, Converter={StaticResource Int32ToVisibilityRevertConverter}}">
<shci:CachedImage
Height="120"
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
EnableLazyLoading="False"
Source="{StaticResource UI_EmotionIcon89}"/>
<TextBlock
Margin="0,5,0,21"
HorizontalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{shcm:ResourceString Name=ControlAutoSuggestBoxNotFoundValue}"/>
</StackPanel>
<ScrollViewer Visibility="{Binding Avatars.Count, Converter={StaticResource Int32ToVisibilityConverter}}">
<StackPanel HorizontalAlignment="Left" Spacing="16">
<!-- 简介 -->
<Grid Style="{ThemeResource GridCardStyle}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
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"
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>
</Border>
<!-- 立绘 -->
<cwc:ConstrainedBox AspectRatio="2048:1024">
<Grid Style="{ThemeResource GridCardStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- 使其有较低的ZOrder -->
<shci:CachedImage Grid.ColumnSpan="2" Source="{Binding Selected.Icon, Converter={StaticResource GachaAvatarImgConverter}}"/>
<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>
<shvcont:ItemIcon
Width="128"
Height="128"
Icon="{Binding Selected.Icon, Converter={StaticResource AvatarIconConverter}, Mode=OneWay}"
Quality="{Binding Selected.Quality, Mode=OneWay}"/>
</StackPanel>
<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>
<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>
</cwc:ConstrainedBox>
<!-- 料理 -->
<Border Style="{ThemeResource BorderCardStyle}">
<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>
<!-- 属性 -->
<Border Style="{ThemeResource BorderCardStyle}">
<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>
<shvcont:BaseValueSlider
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
BaseValueInfo="{Binding BaseValueInfo, Mode=OneWay}"/>
</Border>
<!-- 天赋 -->
<Border Padding="16" Style="{ThemeResource BorderCardStyle}">
<shvcont:SkillPivot ItemTemplate="{StaticResource SkillDataTemplate}" Skills="{Binding Selected.SkillDepot.CompositeSkills}"/>
</Border>
<!-- 命座 -->
<Border Padding="16" Style="{ThemeResource BorderCardStyle}">
<shvcont:SkillPivot ItemTemplate="{StaticResource TalentDataTemplate}" Skills="{Binding Selected.SkillDepot.Talents}"/>
</Border>
<!-- 搭配 -->
<Border Padding="16" Style="{ThemeResource BorderCardStyle}">
<Grid ColumnSpacing="8" RowSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
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"
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>
</Border>
<!-- 立绘 -->
<cwc:ConstrainedBox AspectRatio="2048:1024">
<Grid Style="{ThemeResource GridCardStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock
<!-- 使其有较低的ZOrder -->
<shci:CachedImage Grid.ColumnSpan="2" Source="{Binding Selected.Icon, Converter={StaticResource GachaAvatarImgConverter}}"/>
<Border
Grid.Column="0"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarSpecialFoodTitle}"/>
<shvcont:BottomTextControl
Grid.Row="1"
Grid.Column="0"
Margin="0,16,0,0"
Text="{Binding Item.Name}">
<shvcont:ItemIcon Icon="{Binding Item.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding Item.RankLevel}"/>
</shvcont:BottomTextControl>
<TextBlock
Grid.Column="1"
Margin="16,0,0,0"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarOriginalFoodTitle}"/>
<shvcont:BottomTextControl
Grid.Row="1"
Grid.Column="1"
Margin="16,16,0,0"
Text="{Binding OriginItem.Name}">
<shvcont:ItemIcon Icon="{Binding OriginItem.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding OriginItem.RankLevel}"/>
</shvcont: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>
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>
</Expander>
</Border>
</cwc:ConstrainedBox>
<!-- 衣装 -->
<Border Style="{ThemeResource BorderCardStyle}">
<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 Style="{ThemeResource BorderCardStyle}">
<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}"/>
<shvcont:BottomTextControl
Grid.Row="1"
Grid.Column="0"
Margin="0,16,0,0"
Text="{Binding Item.Name}">
<shvcont:ItemIcon Icon="{Binding Item.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding Item.RankLevel}"/>
</shvcont:BottomTextControl>
<TextBlock
Grid.Column="1"
Margin="16,0,0,0"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageWiKiAvatarOriginalFoodTitle}"/>
<shvcont:BottomTextControl
Grid.Row="1"
Grid.Column="1"
Margin="16,16,0,0"
Text="{Binding OriginItem.Name}">
<shvcont:ItemIcon Icon="{Binding OriginItem.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding OriginItem.RankLevel}"/>
</shvcont: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 Style="{ThemeResource BorderCardStyle}">
<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 Style="{ThemeResource BorderCardStyle}">
<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 Style="{ThemeResource BorderCardStyle}">
<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>
</StackPanel>
</ScrollViewer>
<!-- 资料 -->
<Border Style="{ThemeResource BorderCardStyle}">
<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 Style="{ThemeResource BorderCardStyle}">
<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>
</StackPanel>
</ScrollViewer>
</Grid>
</SplitView.Content>
</SplitView>
</Border>
@@ -736,19 +745,33 @@
<cwc:Case Value="Grid">
<Border Margin="16,0,16,16" cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Border Style="{ThemeResource AcrylicBorderCardStyle}">
<GridView
Padding="16,16,4,4"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemTemplate="{StaticResource AvatarGridTemplate}"
ItemsSource="{Binding Avatars}"
SelectedItem="{Binding Selected, Mode=TwoWay}"
SelectionMode="Single">
<mxi:Interaction.Behaviors>
<shcb:SelectedItemInViewBehavior/>
</mxi:Interaction.Behaviors>
</GridView>
<Grid Padding="16,16,4,4">
<StackPanel VerticalAlignment="Center" Visibility="{Binding Avatars.Count, Converter={StaticResource Int32ToVisibilityRevertConverter}}">
<shci:CachedImage
Height="120"
MinWidth="{ThemeResource SettingsCardContentControlMinWidth}"
EnableLazyLoading="False"
Source="{StaticResource UI_EmotionIcon89}"/>
<TextBlock
Margin="0,5,0,21"
HorizontalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{shcm:ResourceString Name=ControlAutoSuggestBoxNotFoundValue}"/>
</StackPanel>
<GridView
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemTemplate="{StaticResource AvatarGridTemplate}"
ItemsSource="{Binding Avatars}"
SelectedItem="{Binding Selected, Mode=TwoWay}"
SelectionMode="Single"
Visibility="{Binding Avatars.Count, Converter={StaticResource Int32ToVisibilityConverter}}">
<mxi:Interaction.Behaviors>
<shcb:SelectedItemInViewBehavior/>
</mxi:Interaction.Behaviors>
</GridView>
</Grid>
</Border>
</Border>
</cwc:Case>

Some files were not shown because too many files have changed in this diff Show More