update to was 1.3 and fix upload spiralabyss

This commit is contained in:
Lightczx
2023-04-14 15:48:00 +08:00
parent cb882ab062
commit b8895d8250
32 changed files with 770 additions and 475 deletions

View File

@@ -26,6 +26,7 @@ internal sealed class ExceptionRecorder
application.UnhandledException += OnAppUnhandledException;
application.DebugSettings.BindingFailed += OnXamlBindingFailed;
application.DebugSettings.XamlResourceReferenceFailed += OnXamlResourceReferenceFailed;
}
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
@@ -51,4 +52,9 @@ internal sealed class ExceptionRecorder
{
logger.LogCritical("XAML绑定失败: {message}", e.Message);
}
private void OnXamlResourceReferenceFailed(DebugSettings sender, XamlResourceReferenceFailedEventArgs e)
{
logger.LogCritical("XAML资源引用失败: {message}", e.Message);
}
}

View File

@@ -3,9 +3,12 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Message;
using Snap.Hutao.Service;
using Snap.Hutao.Win32;
using System.IO;
using Windows.Graphics;
@@ -27,8 +30,6 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
private readonly ILogger<ExtendedWindow<TWindow>> logger;
private readonly WindowSubclass<TWindow> subclass;
private SystemBackdrop? systemBackdrop;
private ExtendedWindow(TWindow window, FrameworkElement titleBar, IServiceProvider serviceProvider)
{
options = new(window, titleBar);
@@ -54,7 +55,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
/// <inheritdoc/>
public void Receive(FlyoutOpenCloseMessage message)
{
UpdateDragRectangles(options.AppWindow.TitleBar, message.IsOpen);
UpdateDragRectangles(message.IsOpen);
}
private void InitializeWindow()
@@ -69,9 +70,9 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
// appWindow.Show can't bring window to top.
options.Window.Activate();
systemBackdrop = new(options.Window, serviceProvider);
bool micaApplied = systemBackdrop.Update();
logger.LogInformation("Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed");
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
UpdateSystemBackdrop(appOptions.BackdropType);
appOptions.PropertyChanged += OnOptionsPropertyChanged;
bool subClassApplied = subclass.Initialize();
logger.LogInformation("Apply {name} : {result}", nameof(WindowSubclass<TWindow>), subClassApplied ? "succeed" : "failed");
@@ -82,6 +83,14 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
options.Window.Closed += OnWindowClosed;
}
private void OnOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(AppOptions.BackdropType))
{
UpdateSystemBackdrop(((AppOptions)sender!).BackdropType);
}
}
private void OnWindowClosed(object sender, WindowEventArgs args)
{
if (options.Window.PersistSize)
@@ -106,15 +115,28 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
appTitleBar.IconShowOptions = IconShowOptions.HideIconAndSystemMenu;
appTitleBar.ExtendsContentIntoTitleBar = true;
UpdateTitleButtonColor(appTitleBar);
UpdateDragRectangles(appTitleBar);
options.TitleBar.ActualThemeChanged += (s, e) => UpdateTitleButtonColor(appTitleBar);
options.TitleBar.SizeChanged += (s, e) => UpdateDragRectangles(appTitleBar);
UpdateTitleButtonColor();
UpdateDragRectangles();
options.TitleBar.ActualThemeChanged += (s, e) => UpdateTitleButtonColor();
options.TitleBar.SizeChanged += (s, e) => UpdateDragRectangles();
}
}
private void UpdateTitleButtonColor(AppWindowTitleBar appTitleBar)
private void UpdateSystemBackdrop(BackdropType backdropType)
{
options.Window.SystemBackdrop = backdropType switch
{
BackdropType.MicaAlt => new MicaBackdrop() { Kind = MicaKind.BaseAlt },
BackdropType.Mica => new MicaBackdrop() { Kind = MicaKind.Base },
BackdropType.Acrylic => new DesktopAcrylicBackdrop(),
_ => null,
};
}
private void UpdateTitleButtonColor()
{
AppWindowTitleBar appTitleBar = options.AppWindow.TitleBar;
appTitleBar.ButtonBackgroundColor = Colors.Transparent;
appTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
@@ -137,8 +159,10 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
appTitleBar.ButtonPressedForegroundColor = systemBaseHighColor;
}
private void UpdateDragRectangles(AppWindowTitleBar appTitleBar, bool isFlyoutOpened = false)
private void UpdateDragRectangles(bool isFlyoutOpened = false)
{
AppWindowTitleBar appTitleBar = options.AppWindow.TitleBar;
if (isFlyoutOpened)
{
// set to 0

View File

@@ -1,181 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Snap.Hutao.Control.Theme;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service;
using System.Runtime.InteropServices;
using Windows.System;
using WinRT;
namespace Snap.Hutao.Core.Windowing;
/// <summary>
/// 系统背景帮助类
/// </summary>
internal sealed class SystemBackdrop
{
private readonly Window window;
private DispatcherQueueHelper? dispatcherQueueHelper;
private ISystemBackdropControllerWithTargets? backdropController;
private SystemBackdropConfiguration? configuration;
private BackdropType type;
/// <summary>
/// 构造一个新的系统背景帮助类
/// </summary>
/// <param name="window">窗体</param>
/// <param name="serviceProvider">服务提供器</param>
public SystemBackdrop(Window window, IServiceProvider serviceProvider)
{
this.window = window;
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
type = appOptions.BackdropType;
appOptions.PropertyChanged += OnOptionsPropertyChanged;
}
/// <summary>
/// 尝试设置背景
/// </summary>
/// <returns>是否设置成功</returns>
public bool Update()
{
bool isSupport = type switch
{
BackdropType.Acrylic => DesktopAcrylicController.IsSupported(),
BackdropType.Mica or BackdropType.MicaAlt => MicaController.IsSupported(),
_ => false,
};
if (!isSupport)
{
return false;
}
else
{
// Previous one
if (backdropController != null)
{
backdropController.RemoveAllSystemBackdropTargets();
}
else
{
dispatcherQueueHelper = new();
dispatcherQueueHelper.Ensure();
}
// Hooking up the policy object
configuration = new()
{
IsInputActive = true, // Initial configuration state.
};
SetConfigurationSourceTheme(configuration);
window.Activated += OnWindowActivated;
window.Closed += OnWindowClosed;
((FrameworkElement)window.Content).ActualThemeChanged += OnWindowThemeChanged;
backdropController = type switch
{
BackdropType.Acrylic => new DesktopAcrylicController(),
BackdropType.Mica => new MicaController() { Kind = MicaKind.Base },
BackdropType.MicaAlt => new MicaController() { Kind = MicaKind.BaseAlt },
_ => throw Must.NeverHappen(),
};
backdropController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
backdropController.SetSystemBackdropConfiguration(configuration);
return true;
}
}
private void OnOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(AppOptions.BackdropType))
{
type = ((AppOptions)sender!).BackdropType;
Update();
}
}
private void OnWindowActivated(object sender, WindowActivatedEventArgs args)
{
configuration!.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
}
private void OnWindowClosed(object sender, WindowEventArgs args)
{
// Make sure any Mica/Acrylic controller is disposed so it doesn't try to
// use this closed window.
if (backdropController != null)
{
backdropController.Dispose();
backdropController = null;
}
window.Activated -= OnWindowActivated;
configuration = null;
}
private void OnWindowThemeChanged(FrameworkElement sender, object args)
{
if (configuration != null)
{
SetConfigurationSourceTheme(configuration);
}
}
private void SetConfigurationSourceTheme(SystemBackdropConfiguration configuration)
{
configuration.Theme = ThemeHelper.ElementToSystemBackdrop(((FrameworkElement)window.Content).ActualTheme);
}
private class DispatcherQueueHelper
{
private object? dispatcherQueueController;
/// <summary>
/// 确保系统调度队列控制器存在
/// </summary>
public unsafe void Ensure()
{
if (DispatcherQueue.GetForCurrentThread() != null)
{
// one already exists, so we'll just use it.
return;
}
if (dispatcherQueueController == null)
{
DispatcherQueueOptions options = new()
{
DwSize = sizeof(DispatcherQueueOptions),
ThreadType = 2, // DQTYPE_THREAD_CURRENT
ApartmentType = 2, // DQTAT_COM_STA
};
_ = CreateDispatcherQueueController(options, ref dispatcherQueueController);
}
}
[DllImport("CoreMessaging.dll")]
private static extern int CreateDispatcherQueueController(
[In] DispatcherQueueOptions options,
[In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object? dispatcherQueueController);
private struct DispatcherQueueOptions
{
internal int DwSize;
internal int ThreadType;
internal int ApartmentType;
}
}
}

View File

@@ -41,13 +41,16 @@ internal readonly struct WindowOptions<TWindow>
/// </summary>
public readonly bool UseLegacyDragBarImplementation = !AppWindowTitleBar.IsCustomizationSupported();
/// <summary>
/// 构造一个新的窗体选项
/// </summary>
/// <param name="window">窗体</param>
/// <param name="titleBar">标题栏</param>
public WindowOptions(TWindow window, FrameworkElement titleBar)
{
Window = window;
Hwnd = (HWND)WindowNative.GetWindowHandle(window);
WindowId windowId = Win32Interop.GetWindowIdFromWindow(Hwnd);
AppWindow = AppWindow.GetFromWindowId(windowId);
AppWindow = window.AppWindow;
TitleBar = titleBar;
}
}

View File

@@ -1,10 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
@@ -37,31 +35,22 @@ internal sealed class GachaStatisticsFactory : IGachaStatisticsFactory
}
/// <inheritdoc/>
public async Task<GachaStatistics> CreateAsync(IEnumerable<GachaItem> items)
public async Task<GachaStatistics> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context)
{
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
Dictionary<WeaponId, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
Dictionary<string, Avatar> nameAvatarMap = await metadataService.GetNameToAvatarMapAsync().ConfigureAwait(false);
Dictionary<string, Weapon> nameWeaponMap = await metadataService.GetNameToWeaponMapAsync().ConfigureAwait(false);
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
List<HistoryWishBuilder> historyWishBuilders = gachaEvents.Select(g => new HistoryWishBuilder(g, nameAvatarMap, nameWeaponMap)).ToList();
List<HistoryWishBuilder> historyWishBuilders = gachaEvents.Select(g => new HistoryWishBuilder(g, context)).ToList();
IOrderedEnumerable<GachaItem> orderedItems = items.OrderBy(i => i.Id);
await ThreadHelper.SwitchToBackgroundAsync();
return CreateCore(orderedItems, historyWishBuilders, idAvatarMap, idWeaponMap, options.IsEmptyHistoryWishVisible);
return CreateCore(items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);
}
private static GachaStatistics CreateCore(
IOrderedEnumerable<GachaItem> items,
IOrderedQueryable<GachaItem> items,
List<HistoryWishBuilder> historyWishBuilders,
Dictionary<AvatarId, Avatar> avatarMap,
Dictionary<WeaponId, Weapon> weaponMap,
GachaLogServiceContext context,
bool isEmptyHistoryWishVisible)
{
TypedWishSummaryBuilder standardWishBuilder = new(SH.ServiceGachaLogFactoryPermanentWishName, TypedWishSummaryBuilder.IsPermanentWish, 90, 10);
TypedWishSummaryBuilder standardWishBuilder = new(SH.ServiceGachaLogFactoryPermanentWishName, TypedWishSummaryBuilder.IsStandardWish, 90, 10);
TypedWishSummaryBuilder avatarWishBuilder = new(SH.ServiceGachaLogFactoryAvatarWishName, TypedWishSummaryBuilder.IsAvatarEventWish, 90, 10);
TypedWishSummaryBuilder weaponWishBuilder = new(SH.ServiceGachaLogFactoryWeaponWishName, TypedWishSummaryBuilder.IsWeaponEventWish, 80, 10);
@@ -81,58 +70,62 @@ internal sealed class GachaStatisticsFactory : IGachaStatisticsFactory
.SingleOrDefault(w => w.From <= item.Time && w.To >= item.Time);
// It's an avatar
if (item.ItemId.Place() == 8)
switch (item.ItemId.Place())
{
Avatar avatar = avatarMap[item.ItemId];
case 8:
{
Avatar avatar = context.IdAvatarMap[item.ItemId];
bool isUp = false;
switch (avatar.Quality)
{
case ItemQuality.QUALITY_ORANGE:
orangeAvatarCounter.Increase(avatar);
isUp = targetHistoryWishBuilder?.IncreaseOrange(avatar) ?? false;
break;
case ItemQuality.QUALITY_PURPLE:
purpleAvatarCounter.Increase(avatar);
targetHistoryWishBuilder?.IncreasePurple(avatar);
break;
}
bool isUp = false;
switch (avatar.Quality)
{
case ItemQuality.QUALITY_ORANGE:
orangeAvatarCounter.Increase(avatar);
isUp = targetHistoryWishBuilder?.IncreaseOrange(avatar) ?? false;
break;
case ItemQuality.QUALITY_PURPLE:
purpleAvatarCounter.Increase(avatar);
targetHistoryWishBuilder?.IncreasePurple(avatar);
break;
}
standardWishBuilder.Track(item, avatar, isUp);
avatarWishBuilder.Track(item, avatar, isUp);
weaponWishBuilder.Track(item, avatar, isUp);
}
// It's a weapon
else if (item.ItemId.Place() == 5)
{
Weapon weapon = weaponMap[item.ItemId];
bool isUp = false;
switch (weapon.RankLevel)
{
case ItemQuality.QUALITY_ORANGE:
isUp = targetHistoryWishBuilder?.IncreaseOrange(weapon) ?? false;
orangeWeaponCounter.Increase(weapon);
standardWishBuilder.Track(item, avatar, isUp);
avatarWishBuilder.Track(item, avatar, isUp);
weaponWishBuilder.Track(item, avatar, isUp);
break;
case ItemQuality.QUALITY_PURPLE:
targetHistoryWishBuilder?.IncreasePurple(weapon);
purpleWeaponCounter.Increase(weapon);
break;
case ItemQuality.QUALITY_BLUE:
targetHistoryWishBuilder?.IncreaseBlue(weapon);
blueWeaponCounter.Increase(weapon);
break;
}
}
standardWishBuilder.Track(item, weapon, isUp);
avatarWishBuilder.Track(item, weapon, isUp);
weaponWishBuilder.Track(item, weapon, isUp);
}
else
{
// ItemId place not correct.
ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceGachaStatisticsFactoryItemIdInvalid, item.ItemId), null!);
case 5:
{
Weapon weapon = context.IdWeaponMap[item.ItemId];
bool isUp = false;
switch (weapon.RankLevel)
{
case ItemQuality.QUALITY_ORANGE:
isUp = targetHistoryWishBuilder?.IncreaseOrange(weapon) ?? false;
orangeWeaponCounter.Increase(weapon);
break;
case ItemQuality.QUALITY_PURPLE:
targetHistoryWishBuilder?.IncreasePurple(weapon);
purpleWeaponCounter.Increase(weapon);
break;
case ItemQuality.QUALITY_BLUE:
targetHistoryWishBuilder?.IncreaseBlue(weapon);
blueWeaponCounter.Increase(weapon);
break;
}
standardWishBuilder.Track(item, weapon, isUp);
avatarWishBuilder.Track(item, weapon, isUp);
weaponWishBuilder.Track(item, weapon, isUp);
break;
}
default:
// ItemId place not correct.
ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceGachaStatisticsFactoryItemIdInvalid, item.ItemId), null!);
break;
}
}
@@ -161,4 +154,4 @@ internal sealed class GachaStatisticsFactory : IGachaStatisticsFactory
WeaponWish = weaponWishBuilder.ToTypedWishSummary(),
};
}
}
}

View File

@@ -0,0 +1,87 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog.Factory;
/// <summary>
/// 简化的祈愿统计工厂
/// </summary>
[Injection(InjectAs.Scoped, typeof(IGachaStatisticsSlimFactory))]
internal sealed class GachaStatisticsSlimFactory : IGachaStatisticsSlimFactory
{
/// <inheritdoc/>
public async Task<GachaStatisticsSlim> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context)
{
await ThreadHelper.SwitchToBackgroundAsync();
int standardOrangeTracker = 0;
int standardPurpleTracker = 0;
TypedWishSummarySlim standardWish = new(SH.ServiceGachaLogFactoryPermanentWishName, 90, 10);
int avatarOrangeTracker = 0;
int avatarPurpleTracker = 0;
TypedWishSummarySlim avatarWish = new(SH.ServiceGachaLogFactoryAvatarWishName, 90, 10);
int weaponOrangeTracker = 0;
int weaponPurpleTracker = 0;
TypedWishSummarySlim weaponWish = new(SH.ServiceGachaLogFactoryWeaponWishName, 80, 10);
// O(n) operation
foreach (GachaItem item in items)
{
INameQuality nameQuality = context.GetNameQualityByItemId(item.ItemId);
switch (item.QueryType)
{
case GachaConfigType.StandardWish:
Track(nameQuality, ref standardOrangeTracker, ref standardPurpleTracker);
break;
case GachaConfigType.AvatarEventWish:
case GachaConfigType.AvatarEventWish2:
Track(nameQuality, ref avatarOrangeTracker, ref avatarPurpleTracker);
break;
case GachaConfigType.WeaponEventWish:
Track(nameQuality, ref weaponOrangeTracker, ref weaponPurpleTracker);
break;
}
}
standardWish.LastOrangePull = standardOrangeTracker;
standardWish.LastPurplePull = standardPurpleTracker;
avatarWish.LastOrangePull = avatarOrangeTracker;
avatarWish.LastPurplePull = avatarPurpleTracker;
weaponWish.LastOrangePull = weaponOrangeTracker;
weaponWish.LastPurplePull = weaponPurpleTracker;
return new()
{
AvatarWish = avatarWish,
WeaponWish = weaponWish,
StandardWish = standardWish,
};
}
private static void Track(INameQuality nameQuality, ref int orangeTracker, ref int purpleTracker)
{
switch (nameQuality.Quality)
{
case ItemQuality.QUALITY_ORANGE:
orangeTracker = 0;
++purpleTracker;
break;
case ItemQuality.QUALITY_PURPLE:
++orangeTracker;
purpleTracker = 0;
break;
case ItemQuality.QUALITY_BLUE:
++orangeTracker;
++purpleTracker;
break;
}
}
}

View File

@@ -31,22 +31,21 @@ internal sealed class HistoryWishBuilder
/// 构造一个新的卡池历史记录建造器
/// </summary>
/// <param name="gachaEvent">卡池配置</param>
/// <param name="nameAvatarMap">命名角色映射</param>
/// <param name="nameWeaponMap">命名武器映射</param>
public HistoryWishBuilder(GachaEvent gachaEvent, Dictionary<string, Avatar> nameAvatarMap, Dictionary<string, Weapon> nameWeaponMap)
/// <param name="context">祈愿记录上下文</param>
public HistoryWishBuilder(GachaEvent gachaEvent, GachaLogServiceContext context)
{
this.gachaEvent = gachaEvent;
configType = gachaEvent.Type;
if (configType == GachaConfigType.AvatarEventWish || configType == GachaConfigType.AvatarEventWish2)
{
orangeUpCounter = gachaEvent.UpOrangeList.Select(name => nameAvatarMap[name]).ToDictionary(a => (IStatisticsItemSource)a, a => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(name => nameAvatarMap[name]).ToDictionary(a => (IStatisticsItemSource)a, a => 0);
orangeUpCounter = gachaEvent.UpOrangeList.Select(name => context.NameAvatarMap[name]).ToDictionary(a => (IStatisticsItemSource)a, a => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(name => context.NameAvatarMap[name]).ToDictionary(a => (IStatisticsItemSource)a, a => 0);
}
else if (configType == GachaConfigType.WeaponEventWish)
{
orangeUpCounter = gachaEvent.UpOrangeList.Select(name => nameWeaponMap[name]).ToDictionary(w => (IStatisticsItemSource)w, w => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(name => nameWeaponMap[name]).ToDictionary(w => (IStatisticsItemSource)w, w => 0);
orangeUpCounter = gachaEvent.UpOrangeList.Select(name => context.NameWeaponMap[name]).ToDictionary(w => (IStatisticsItemSource)w, w => 0);
purpleUpCounter = gachaEvent.UpPurpleList.Select(name => context.NameWeaponMap[name]).ToDictionary(w => (IStatisticsItemSource)w, w => 0);
}
}

View File

@@ -16,6 +16,7 @@ internal interface IGachaStatisticsFactory
/// 异步创建祈愿统计对象
/// </summary>
/// <param name="items">物品列表</param>
/// <param name="context">祈愿记录上下文</param>
/// <returns>祈愿统计对象</returns>
Task<GachaStatistics> CreateAsync(IEnumerable<GachaItem> items);
Task<GachaStatistics> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context);
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.ViewModel.GachaLog;
namespace Snap.Hutao.Service.GachaLog.Factory;
/// <summary>
/// 简化的祈愿统计工厂
/// </summary>
internal interface IGachaStatisticsSlimFactory
{
/// <summary>
/// 异步创建一个新的简化的祈愿统计
/// </summary>
/// <param name="items">排序的物品</param>
/// <param name="context">祈愿记录服务上下文</param>
/// <returns>简化的祈愿统计</returns>
Task<GachaStatisticsSlim> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context);
}

View File

@@ -18,7 +18,7 @@ internal sealed class TypedWishSummaryBuilder
/// <summary>
/// 常驻祈愿
/// </summary>
public static readonly Func<GachaConfigType, bool> IsPermanentWish = type => type is GachaConfigType.StandardWish;
public static readonly Func<GachaConfigType, bool> IsStandardWish = type => type is GachaConfigType.StandardWish;
/// <summary>
/// 角色活动

View File

@@ -1,26 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Binding;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.InterChange.GachaLog;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.GachaLog.Factory;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Response;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.GachaLog;
@@ -61,19 +45,9 @@ internal sealed class GachaLogImportService : IGachaLogImportService
IEnumerable<GachaItem> toAdd = list
.OrderByDescending(i => i.Id)
.Where(i => i.Id < trimId)
.Select(i => GachaItem.Create(archiveId, i, GetItemId(context, i)));
.Select(i => GachaItem.Create(archiveId, i, context.GetItemId(i)));
await appDbContext.GachaItems.AddRangeAndSaveAsync(toAdd).ConfigureAwait(false);
return archive;
}
private static int GetItemId(GachaLogServiceContext context, GachaLogItem item)
{
return item.ItemType switch
{
"角色" => context.NameAvatarMap.GetValueOrDefault(item.Name)?.Id ?? 0,
"武器" => context.NameWeaponMap.GetValueOrDefault(item.Name)?.Id ?? 0,
_ => 0,
};
}
}

View File

@@ -34,11 +34,10 @@ internal sealed class GachaLogService : IGachaLogService
private readonly AppDbContext appDbContext;
private readonly IGachaLogExportService gachaLogExportService;
private readonly IGachaLogImportService gachaLogImportService;
private readonly IEnumerable<IGachaLogQueryProvider> urlProviders;
private readonly GachaInfoClient gachaInfoClient;
private readonly IMetadataService metadataService;
private readonly IGachaStatisticsFactory gachaStatisticsFactory;
private readonly IGachaStatisticsSlimFactory gachaStatisticsSlimFactory;
private readonly ILogger<GachaLogService> logger;
private readonly DbCurrent<GachaArchive, Message.GachaArchiveChangedMessage> dbCurrent;
@@ -48,32 +47,17 @@ internal sealed class GachaLogService : IGachaLogService
/// 构造一个新的祈愿记录服务
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="appDbContext">数据库上下文</param>
/// <param name="urlProviders">Url提供器集合</param>
/// <param name="gachaInfoClient">祈愿记录客户端</param>
/// <param name="metadataService">元数据服务</param>
/// <param name="gachaStatisticsFactory">祈愿统计工厂</param>
/// <param name="logger">日志器</param>
/// <param name="messenger">消息器</param>
public GachaLogService(
IServiceProvider serviceProvider,
AppDbContext appDbContext,
IEnumerable<IGachaLogQueryProvider> urlProviders,
GachaInfoClient gachaInfoClient,
IMetadataService metadataService,
IGachaStatisticsFactory gachaStatisticsFactory,
ILogger<GachaLogService> logger,
IMessenger messenger)
public GachaLogService(IServiceProvider serviceProvider, IMessenger messenger)
{
gachaLogExportService = serviceProvider.GetRequiredService<IGachaLogExportService>();
gachaLogImportService = serviceProvider.GetRequiredService<IGachaLogImportService>();
this.appDbContext = appDbContext;
this.urlProviders = urlProviders;
this.gachaInfoClient = gachaInfoClient;
this.metadataService = metadataService;
this.logger = logger;
this.gachaStatisticsFactory = gachaStatisticsFactory;
appDbContext = serviceProvider.GetRequiredService<AppDbContext>();
gachaInfoClient = serviceProvider.GetRequiredService<GachaInfoClient>();
metadataService = serviceProvider.GetRequiredService<IMetadataService>();
logger = serviceProvider.GetRequiredService<ILogger<GachaLogService>>();
gachaStatisticsFactory = serviceProvider.GetRequiredService<IGachaStatisticsFactory>();
gachaStatisticsSlimFactory = serviceProvider.GetRequiredService<IGachaStatisticsSlimFactory>();
dbCurrent = new(appDbContext.GachaArchives, messenger);
}
@@ -127,8 +111,8 @@ internal sealed class GachaLogService : IGachaLogService
{
using (ValueStopwatch.MeasureExecution(logger))
{
IQueryable<GachaItem> items = appDbContext.GachaItems.Where(i => i.ArchiveId == archive.InnerId);
return await gachaStatisticsFactory.CreateAsync(items).ConfigureAwait(false);
IOrderedQueryable<GachaItem> items = appDbContext.GachaItems.Where(i => i.ArchiveId == archive.InnerId).OrderBy(i => i.Id);
return await gachaStatisticsFactory.CreateAsync(items, context).ConfigureAwait(false);
}
}
else
@@ -137,6 +121,24 @@ internal sealed class GachaLogService : IGachaLogService
}
}
/// <inheritdoc/>
public async Task<List<GachaStatisticsSlim>> GetStatisticsSlimsAsync()
{
List<GachaStatisticsSlim> statistics = new();
foreach (GachaArchive archive in appDbContext.GachaArchives)
{
IOrderedQueryable<GachaItem> items = appDbContext.GachaItems
.Where(i => i.ArchiveId == archive.InnerId)
.OrderBy(i => i.Id);
GachaStatisticsSlim slim = await gachaStatisticsSlimFactory.CreateAsync(items, context).ConfigureAwait(false);
slim.Uid = archive.Uid;
statistics.Add(slim);
}
return statistics;
}
/// <inheritdoc/>
public Task<UIGF> ExportToUIGFAsync(GachaArchive archive)
{

View File

@@ -40,6 +40,12 @@ internal interface IGachaLogService
/// <returns>祈愿统计</returns>
Task<GachaStatistics> GetStatisticsAsync(GachaArchive? archive);
/// <summary>
/// 异步获取简化的祈愿统计列表
/// </summary>
/// <returns>简化的祈愿统计列表</returns>
Task<List<GachaStatisticsSlim>> GetStatisticsSlimsAsync();
/// <summary>
/// 异步从UIGF导入数据
/// </summary>

View File

@@ -25,7 +25,7 @@ internal sealed class HutaoUserOptions : ObservableObject, IOptions<HutaoUserOpt
/// <summary>
/// 真正的用户名
/// </summary>
public string? ActualUserName { get => IsLoggedIn ? null : UserName; }
public string? ActualUserName { get => IsLoggedIn ? UserName : null; }
/// <summary>
/// 访问令牌

View File

@@ -51,7 +51,6 @@ internal sealed class HutaoUserService : IHutaoUserService, IHutaoUserServiceIni
{
await ThreadHelper.SwitchToMainThreadAsync();
options.LoginSucceed(userName, response.Data);
isInitialized = true;
}
else

View File

@@ -106,12 +106,15 @@
<None Remove="Resource\Segoe Fluent Icons.ttf" />
<None Remove="Resource\WelcomeView_Background.png" />
<None Remove="stylecop.json" />
<None Remove="View\Card\GachaStatisticsCard.xaml" />
<None Remove="View\Card\LaunchGameCard.xaml" />
<None Remove="View\Control\BaseValueSlider.xaml" />
<None Remove="View\Control\BottomTextControl.xaml" />
<None Remove="View\Control\DescParamComboBox.xaml" />
<None Remove="View\Control\ItemIcon.xaml" />
<None Remove="View\Control\LaunchGameResourceExpander.xaml" />
<None Remove="View\Control\LoadingView.xaml" />
<None Remove="View\Control\LoadingViewSlim.xaml" />
<None Remove="View\Control\SkillPivot.xaml" />
<None Remove="View\Control\StatisticsCard.xaml" />
<None Remove="View\Dialog\AchievementArchiveCreateDialog.xaml" />
@@ -236,8 +239,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -254,7 +257,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.756" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.230313.1" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.3.230331000" />
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -293,6 +296,18 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Page Update="View\Card\GachaStatisticsCard.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Control\LoadingViewSlim.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Page\HutaoPassportPage.xaml">
<Generator>MSBuild:Compile</Generator>
@@ -540,4 +555,9 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Card\LaunchGameCard.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,286 @@
<Button
x:Class="Snap.Hutao.View.Card.GachaStatisticsCard"
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"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvco="using:Snap.Hutao.View.Control"
xmlns:shvg="using:Snap.Hutao.ViewModel.GachaLog"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
d:DataContext="{d:DesignInstance shvg:GachaLogViewModelSlim}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">
<mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
</mxi:Interaction.Behaviors>
<Grid>
<FlipView
Background="{x:Null}"
ItemsSource="{Binding StatisticsList}"
Visibility="{Binding IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}">
<FlipView.ItemTemplate>
<DataTemplate>
<Grid Margin="12" ColumnSpacing="6">
<Grid.Resources>
<SolidColorBrush x:Key="PurpleBrush" Color="#FFA156E0"/>
<SolidColorBrush x:Key="OrangeBrush" Color="#FFBC6932"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" DataContext="{Binding AvatarWish}">
<TextBlock
Margin="0,0,0,6"
HorizontalAlignment="Center"
Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"/>
<Border Grid.Column="0" Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="40"
Height="40"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
Foreground="{StaticResource OrangeBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteeOrangeThreshold}"
Value="{Binding LastOrangePull}"/>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding LastOrangePull}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
</Grid>
</Border>
<Border
Grid.Column="1"
Margin="0,6,0,0"
Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="40"
Height="40"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
Foreground="{StaticResource PurpleBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteePurpleThreshold}"
Value="{Binding LastPurplePull}"/>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding LastPurplePull}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
</Grid>
</Border>
</StackPanel>
<StackPanel Grid.Column="1" DataContext="{Binding WeaponWish}">
<TextBlock
Margin="0,0,0,6"
HorizontalAlignment="Center"
Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"/>
<Border Grid.Column="0" Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="40"
Height="40"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
Foreground="{StaticResource OrangeBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteeOrangeThreshold}"
Value="{Binding LastOrangePull}"/>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding LastOrangePull}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
</Grid>
</Border>
<Border
Grid.Column="1"
Margin="0,6,0,0"
Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="40"
Height="40"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
Foreground="{StaticResource PurpleBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteePurpleThreshold}"
Value="{Binding LastPurplePull}"/>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding LastPurplePull}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
</Grid>
</Border>
</StackPanel>
<StackPanel Grid.Column="2" DataContext="{Binding StandardWish}">
<TextBlock
Margin="0,0,0,6"
HorizontalAlignment="Center"
Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"/>
<Border Grid.Column="0" Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="40"
Height="40"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
Foreground="{StaticResource OrangeBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteeOrangeThreshold}"
Value="{Binding LastOrangePull}"/>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding LastOrangePull}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource OrangeBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardOrangeText}"/>
</Grid>
</Border>
<Border
Grid.Column="1"
Margin="0,6,0,0"
Style="{StaticResource BorderCardStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressRing
Grid.Column="0"
Width="40"
Height="40"
Margin="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
Foreground="{StaticResource PurpleBrush}"
IsIndeterminate="False"
Maximum="{Binding GuaranteePurpleThreshold}"
Value="{Binding LastPurplePull}"/>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding LastPurplePull}"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource PurpleBrush}"
Style="{StaticResource BaseTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewControlStatisticsCardPurpleText}"/>
</Grid>
</Border>
</StackPanel>
<TextBlock
Grid.Row="1"
Grid.ColumnSpan="3"
Text="{Binding Uid}"/>
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
<shvco:LoadingViewSlim IsLoading="{Binding IsInitialized, Converter={StaticResource BoolNegationConverter}}"/>
</Grid>
</Button>

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.View.Card;
/// <summary>
/// <20><>Ը<EFBFBD><D4B8>¼<EFBFBD><C2BC>Ƭ
/// </summary>
internal sealed partial class GachaStatisticsCard : Button
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD><C2B5><EFBFBD>Ը<EFBFBD><D4B8>¼<EFBFBD><C2BC>Ƭ
/// </summary>
public GachaStatisticsCard()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,73 @@
<Button
x:Class="Snap.Hutao.View.Card.LaunchGameCard"
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"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvg="using:Snap.Hutao.ViewModel.Game"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
d:DataContext="{d:DesignInstance shvg:LaunchGameViewModelSlim}"
Command="{Binding LaunchCommand}"
Style="{ThemeResource DefaultButtonStyle}"
mc:Ignorable="d">
<mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
</mxi:Interaction.Behaviors>
<Grid CornerRadius="{ThemeResource ControlCornerRadius}">
<Image
Margin="0,40,0,0"
HorizontalAlignment="Right"
Opacity="0.4"
Source="ms-appx:///Resource/HutaoIconSourceTransparentBackgroundGradient1.png"
Stretch="Uniform"/>
<Grid Margin="12" ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<FontIcon
Grid.Row="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="{ThemeResource TitleTextBlockFontSize}"
Glyph="&#xE7FC;"/>
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Foreground="{ThemeResource SystemAccentColorLight3}"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewLaunchGameHeader}"/>
<Button
Grid.Column="2"
MinHeight="37.2"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding NavigateCommand}"
Content="&#xE713;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
ToolTipService.ToolTip="{shcm:ResourceString Name=ViewPageHomeLaunchGameSettingAction}"/>
<ComboBox
Grid.Row="2"
Grid.ColumnSpan="3"
VerticalAlignment="Bottom"
DisplayMemberPath="Name"
ItemsSource="{Binding GameAccounts}"
PlaceholderText="选择账号或直接启动"
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
</Grid>
</Grid>
</Button>

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.View.Card;
/// <summary>
/// 启动游戏卡片
/// </summary>
internal sealed partial class LaunchGameCard : Button
{
/// <summary>
/// 构造一个新的启动游戏卡片
/// </summary>
public LaunchGameCard()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,13 @@
<cwuc:Loading
x:Class="Snap.Hutao.View.Control.LoadingViewSlim"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ProgressRing IsActive="True"/>
</StackPanel>
</cwuc:Loading>

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Controls;
namespace Snap.Hutao.View.Control;
/// <summary>
/// <20>򵥵ļ<F2B5A5B5><C4BC><EFBFBD><EFBFBD><EFBFBD>ͼ
/// </summary>
public sealed partial class LoadingViewSlim : Loading
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µļ򵥵ļ<F2B5A5B5><C4BC><EFBFBD><EFBFBD><EFBFBD>ͼ
/// </summary>
public LoadingViewSlim()
{
InitializeComponent();
}
}

View File

@@ -17,7 +17,8 @@
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shv="using:Snap.Hutao.ViewModel"
xmlns:shvc="using:Snap.Hutao.View.Control"
xmlns:shvca="using:Snap.Hutao.View.Card"
xmlns:shvco="using:Snap.Hutao.View.Control"
d:DataContext="{d:DesignInstance shv:AnnouncementViewModel}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
@@ -132,7 +133,7 @@
<Setter Property="MaxWidth" Value="640"/>
</Style>
</Flyout.FlyoutPresenterStyle>
<shvc:AnnouncementContentViewer Announcement="{Binding}"/>
<shvco:AnnouncementContentViewer Announcement="{Binding}"/>
</Flyout>
</FlyoutBase.AttachedFlyout>
<mxi:Interaction.Behaviors>
@@ -168,137 +169,8 @@
DesiredWidth="300"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
SelectionMode="None">
<!-- 启动游戏 -->
<Button
Height="120"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
Command="{Binding LaunchGameViewModelSlim.LaunchCommand}"
Style="{StaticResource AccentButtonStyle}">
<mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding LaunchGameViewModelSlim.OpenUICommand}"/>
</mxi:Interaction.Behaviors>
<Grid CornerRadius="{ThemeResource ControlCornerRadius}">
<Image
Margin="0,40,0,0"
HorizontalAlignment="Right"
Source="ms-appx:///Resource/HutaoIconSourceTransparentBackgroundGradient1.png"
Stretch="Uniform"/>
<Grid Margin="12" ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<FontIcon
Grid.Row="0"
VerticalAlignment="Center"
Glyph="&#xE7FC;"/>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{shcm:ResourceString Name=ViewLaunchGameHeader}"/>
<Button
Grid.Column="2"
MinHeight="37.2"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding LaunchGameViewModelSlim.NavigateCommand}"
Content="&#xE713;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
Foreground="{ThemeResource AccentButtonForeground}"
ToolTipService.ToolTip="{shcm:ResourceString Name=ViewPageHomeLaunchGameSettingAction}"/>
<ComboBox
Grid.Row="1"
Grid.ColumnSpan="3"
VerticalAlignment="Bottom"
DisplayMemberPath="Name"
ItemsSource="{Binding LaunchGameViewModelSlim.GameAccounts}"
PlaceholderText="选择账号或直接启动"
SelectedItem="{Binding LaunchGameViewModelSlim.SelectedGameAccount, Mode=TwoWay}"/>
</Grid>
</Grid>
</Button>
<Border Style="{StaticResource BorderCardStyle}">
<FlipView Background="{x:Null}">
<Grid Margin="12">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="卡池名称 1"/>
<shvc:ItemIcon
Width="40"
Height="40"
Margin="0,12,0,0"
HorizontalAlignment="Left"
Icon="{Binding Icon}"
Quality="QUALITY_ORANGE"/>
<TextBlock Margin="0,6,0,0" Text="XX 抽"/>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="卡池名称 1"/>
<shvc:ItemIcon
Width="40"
Height="40"
Margin="0,12,0,0"
HorizontalAlignment="Left"
Icon="{Binding Icon}"
Quality="QUALITY_ORANGE"/>
<TextBlock Margin="0,6,0,0" Text="XX 抽"/>
</StackPanel>
</Grid>
<Grid Margin="12">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="卡池名称 1"/>
<shvc:ItemIcon
Width="40"
Height="40"
Margin="0,12,0,0"
HorizontalAlignment="Left"
Icon="{Binding Icon}"
Quality="QUALITY_ORANGE"/>
<TextBlock Margin="0,6,0,0" Text="XX 抽"/>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="卡池名称 1"/>
<shvc:ItemIcon
Width="40"
Height="40"
Margin="0,12,0,0"
HorizontalAlignment="Left"
Icon="{Binding Icon}"
Quality="QUALITY_ORANGE"/>
<TextBlock Margin="0,6,0,0" Text="XX 抽"/>
</StackPanel>
</Grid>
</FlipView>
</Border>
<shvca:LaunchGameCard Height="186" DataContext="{Binding LaunchGameViewModelSlim}"/>
<shvca:GachaStatisticsCard DataContext="{Binding GachaLogViewModelSlim}"/>
<Border Style="{StaticResource BorderCardStyle}">
<FlipView Background="{x:Null}">

View File

@@ -3,7 +3,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Snap.Hutao.Service.Navigation;
namespace Snap.Hutao.ViewModel.Abstraction;

View File

@@ -14,6 +14,8 @@ namespace Snap.Hutao.ViewModel.Abstraction;
internal abstract class ViewModelSlim<TPage> : ObservableObject
where TPage : Microsoft.UI.Xaml.Controls.Page
{
private bool isInitialized;
/// <summary>
/// 构造一个新的简化的视图模型抽象类
/// </summary>
@@ -26,6 +28,11 @@ internal abstract class ViewModelSlim<TPage> : ObservableObject
NavigateCommand = new RelayCommand(Navigate);
}
/// <summary>
/// 是否初始化完成
/// </summary>
public bool IsInitialized { get => isInitialized; set => SetProperty(ref isInitialized, value); }
/// <summary>
/// 打开页面命令
/// </summary>

View File

@@ -26,6 +26,7 @@ internal sealed class AnnouncementViewModel : Abstraction.ViewModel
announcementService = serviceProvider.GetRequiredService<IAnnouncementService>();
LaunchGameViewModelSlim = serviceProvider.GetRequiredService<Game.LaunchGameViewModelSlim>();
GachaLogViewModelSlim = serviceProvider.GetRequiredService<GachaLog.GachaLogViewModelSlim>();
}
/// <summary>
@@ -38,6 +39,11 @@ internal sealed class AnnouncementViewModel : Abstraction.ViewModel
/// </summary>
public Game.LaunchGameViewModelSlim LaunchGameViewModelSlim { get; }
/// <summary>
/// 祈愿记录视图模型
/// </summary>
public GachaLog.GachaLogViewModelSlim GachaLogViewModelSlim { get; }
/// <inheritdoc/>
protected override async Task OpenUIAsync()
{

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Service.GachaLog;
namespace Snap.Hutao.ViewModel.GachaLog;
/// <summary>
@@ -26,8 +28,16 @@ internal sealed class GachaLogViewModelSlim : Abstraction.ViewModelSlim<View.Pag
public List<GachaStatisticsSlim>? StatisticsList { get => statisticsList; set => SetProperty(ref statisticsList, value); }
/// <inheritdoc/>
protected override Task OpenUIAsync()
protected override async Task OpenUIAsync()
{
return Task.CompletedTask;
IGachaLogService gachaLogService = ServiceProvider.GetRequiredService<IGachaLogService>();
if (await gachaLogService.InitializeAsync(default).ConfigureAwait(false))
{
List<GachaStatisticsSlim> list = await gachaLogService.GetStatisticsSlimsAsync().ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
StatisticsList = list;
IsInitialized = true;
}
}
}

View File

@@ -16,15 +16,15 @@ internal sealed class GachaStatisticsSlim
/// <summary>
/// 角色活动
/// </summary>
public TypedWishSummary AvatarWish { get; set; } = default!;
public TypedWishSummarySlim AvatarWish { get; set; } = default!;
/// <summary>
/// 神铸赋形
/// </summary>
public TypedWishSummary WeaponWish { get; set; } = default!;
public TypedWishSummarySlim WeaponWish { get; set; } = default!;
/// <summary>
/// 奔行世间
/// </summary>
public TypedWishSummary StandardWish { get; set; } = default!;
public TypedWishSummarySlim StandardWish { get; set; } = default!;
}

View File

@@ -8,6 +8,19 @@ namespace Snap.Hutao.ViewModel.GachaLog;
/// </summary>
internal sealed class TypedWishSummarySlim
{
/// <summary>
/// 构造一个新的简化的类型化的祈愿概览
/// </summary>
/// <param name="name">卡池名称</param>
/// <param name="guaranteeOrangeThreshold">五星保底阈值</param>
/// <param name="guaranteePurpleThreshold">四星保底阈值</param>
public TypedWishSummarySlim(string name, int guaranteeOrangeThreshold, int guaranteePurpleThreshold)
{
Name = name;
GuaranteeOrangeThreshold = guaranteeOrangeThreshold;
GuaranteePurpleThreshold = guaranteePurpleThreshold;
}
/// <summary>
/// 卡池名称
/// </summary>

View File

@@ -23,6 +23,14 @@ internal struct GachaLogQueryOptions
/// </summary>
public readonly bool IsOversea;
/// <summary>
/// 结束Id
/// 控制API返回的分页
/// 米哈游使用了 keyset pagination 来实现这一目标
/// https://learn.microsoft.com/en-us/ef/core/querying/pagination#keyset-pagination
/// </summary>
public long EndId;
/// <summary>
/// Keys required:
/// authkey_ver
@@ -37,14 +45,6 @@ internal struct GachaLogQueryOptions
/// </summary>
private readonly QueryString innerQuery;
/// <summary>
/// 结束Id
/// 控制API返回的分页
/// 米哈游使用了 keyset pagination 来实现这一目标
/// https://learn.microsoft.com/en-us/ef/core/querying/pagination#keyset-pagination
/// </summary>
public long EndId;
/// <summary>
/// 构造一个新的祈愿记录请求配置
/// </summary>

View File

@@ -18,6 +18,7 @@ internal sealed class SimpleRecord
/// <param name="uid">uid</param>
/// <param name="characters">详细的角色信息</param>
/// <param name="spiralAbyss">深渊信息</param>
/// <param name="reservedUserName">用户名</param>
public SimpleRecord(string uid, List<Character> characters, SpiralAbyss spiralAbyss, string? reservedUserName)
{
Uid = uid;

View File

@@ -48,6 +48,7 @@ internal static class HutaoEndpoints
/// <summary>
/// 删除祈愿记录
/// </summary>
/// <param name="uid">uid</param>
/// <returns>删除祈愿记录 Url</returns>
public static string GachaLogDelete(string uid)
{