mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
file nesting
This commit is contained in:
31
src/Snap.Hutao/Snap.Hutao/.filenesting.json
Normal file
31
src/Snap.Hutao/Snap.Hutao/.filenesting.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"help": "https://go.microsoft.com/fwlink/?linkid=866610",
|
||||
"dependentFileProviders": {
|
||||
"add": {
|
||||
"extensionToExtension": {
|
||||
"add": {
|
||||
".json": [ ".txt" ]
|
||||
}
|
||||
},
|
||||
"pathSegment": {
|
||||
"add": {
|
||||
".*": [ ".cs" ]
|
||||
}
|
||||
},
|
||||
"fileSuffixToExtension": {
|
||||
"add": {
|
||||
"DesignTimeFactory.cs": [".cs"]
|
||||
}
|
||||
},
|
||||
"fileToFile": {
|
||||
"add": {
|
||||
"app.manifest": [ "App.xaml.cs" ],
|
||||
"Package.appxmanifest": [ "App.xaml.cs" ],
|
||||
"GlobalUsing.cs": [ "Program.cs" ],
|
||||
".filenesting.json": [ "Program.cs" ],
|
||||
".editorconfig": [ "Program.cs" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/Snap.Hutao/Snap.Hutao/Assets/Logo.ico
Normal file
BIN
src/Snap.Hutao/Snap.Hutao/Assets/Logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -61,6 +61,11 @@ public class AppDbContext : DbContext
|
||||
/// </summary>
|
||||
public DbSet<GameAccount> GameAccounts { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺
|
||||
/// </summary>
|
||||
public DbSet<DailyNoteEntry> DailyNotes { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个临时的应用程序数据库上下文
|
||||
/// </summary>
|
||||
@@ -76,6 +81,7 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
modelBuilder
|
||||
.ApplyConfiguration(new AvatarInfoConfiguration())
|
||||
.ApplyConfiguration(new UserConfiguration());
|
||||
.ApplyConfiguration(new UserConfiguration())
|
||||
.ApplyConfiguration(new DailyNoteEntryConfiguration());
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,12 @@ namespace Snap.Hutao.Core;
|
||||
/// </summary>
|
||||
internal static class CoreEnvironment
|
||||
{
|
||||
// 计算过程:https://gist.github.com/Lightczx/373c5940b36e24b25362728b52dec4fd
|
||||
// 计算过程:https://github.com/UIGF-org/Hoyolab.Salt
|
||||
|
||||
/// <summary>
|
||||
/// 动态密钥1的盐
|
||||
/// </summary>
|
||||
public const string DynamicSecret1Salt = "yUZ3s0Sna1IrSNfk29Vo6vRapdOyqyhB";
|
||||
public const string DynamicSecret1Salt = "jEpJb9rRARU2rXDA9qYbZ3selxkuct9a";
|
||||
|
||||
/// <summary>
|
||||
/// 动态密钥2的盐
|
||||
@@ -35,7 +35,7 @@ internal static class CoreEnvironment
|
||||
/// <summary>
|
||||
/// 米游社 Rpc 版本
|
||||
/// </summary>
|
||||
public const string HoyolabXrpcVersion = "2.38.1";
|
||||
public const string HoyolabXrpcVersion = "2.40.1";
|
||||
|
||||
/// <summary>
|
||||
/// 标准UA
|
||||
|
||||
38
src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs
Normal file
38
src/Snap.Hutao/Snap.Hutao/Core/TaskSchedulerHelper.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Win32.TaskScheduler;
|
||||
using SchedulerTask = Microsoft.Win32.TaskScheduler.Task;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 任务计划器服务
|
||||
/// </summary>
|
||||
internal class TaskSchedulerHelper
|
||||
{
|
||||
private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask";
|
||||
|
||||
/// <summary>
|
||||
/// 注册实时便笺刷新任务
|
||||
/// </summary>
|
||||
/// <param name="interval">间隔(秒)</param>
|
||||
public void RegisterForDailyNoteRefresh(int interval)
|
||||
{
|
||||
TimeSpan intervalTime = TimeSpan.FromSeconds(interval);
|
||||
if (TaskService.Instance.GetTask(DailyNoteRefreshTaskName) is SchedulerTask targetTask)
|
||||
{
|
||||
TimeTrigger? trigger = targetTask.Definition.Triggers[0] as TimeTrigger;
|
||||
trigger!.Repetition.Interval = intervalTime;
|
||||
targetTask.RegisterChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskDefinition task = TaskService.Instance.NewTask();
|
||||
task.RegistrationInfo.Description = "胡桃实时便笺刷新任务 | 请勿编辑或删除。";
|
||||
task.Triggers.Add(new TimeTrigger() { Repetition = new(intervalTime, TimeSpan.Zero), });
|
||||
task.Actions.Add("explorer", "hutao://DailyNote/Refresh");
|
||||
TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Achievement;
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
/// <summary>
|
||||
/// 成就触发器类型
|
||||
/// 背景类型
|
||||
/// </summary>
|
||||
public enum AchievementTriggerType
|
||||
public enum BackdropType
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务
|
||||
/// 无
|
||||
/// </summary>
|
||||
Quest = 1,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 子任务
|
||||
/// 亚克力
|
||||
/// </summary>
|
||||
SubQuest = 2,
|
||||
Acrylic,
|
||||
|
||||
/// <summary>
|
||||
/// 日常任务
|
||||
/// 云母
|
||||
/// </summary>
|
||||
DailyTask = 3,
|
||||
Mica,
|
||||
|
||||
/// <summary>
|
||||
/// 变种云母
|
||||
/// </summary>
|
||||
MicaAlt,
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Message;
|
||||
using Snap.Hutao.Win32;
|
||||
using System.IO;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Graphics;
|
||||
using Windows.UI;
|
||||
using Windows.Win32.Foundation;
|
||||
@@ -15,11 +19,10 @@ using WinRT.Interop;
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
/// <summary>
|
||||
/// 窗口管理器
|
||||
/// 主要包含了针对窗体的 P/Inoke 逻辑
|
||||
/// 扩展窗口
|
||||
/// </summary>
|
||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||
internal sealed class ExtendedWindow<TWindow>
|
||||
internal sealed class ExtendedWindow<TWindow> : IRecipient<BackdropTypeChangedMessage>
|
||||
where TWindow : Window, IExtendedWindowSource
|
||||
{
|
||||
private readonly HWND handle;
|
||||
@@ -33,8 +36,10 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
|
||||
private readonly bool useLegacyDragBar;
|
||||
|
||||
private SystemBackdrop? systemBackdrop;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的窗口状态管理器
|
||||
/// 构造一个新的扩展窗口
|
||||
/// </summary>
|
||||
/// <param name="window">窗口</param>
|
||||
/// <param name="titleBar">充当标题栏的元素</param>
|
||||
@@ -65,6 +70,17 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
return new(window, window.TitleBar);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Receive(BackdropTypeChangedMessage message)
|
||||
{
|
||||
if (systemBackdrop != null)
|
||||
{
|
||||
systemBackdrop.BackdropType = message.BackdropType;
|
||||
bool micaApplied = systemBackdrop.TryApply();
|
||||
logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateTitleButtonColor(AppWindowTitleBar appTitleBar)
|
||||
{
|
||||
appTitleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
@@ -102,7 +118,7 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
private void InitializeWindow()
|
||||
{
|
||||
appWindow.Title = "胡桃";
|
||||
|
||||
appWindow.SetIcon(Path.Combine(Package.Current.InstalledLocation.Path, "Assets/Logos/Logo.ico"));
|
||||
ExtendsContentIntoTitleBar();
|
||||
|
||||
Persistence.RecoverOrInit(appWindow, window.PersistSize, window.InitSize);
|
||||
@@ -113,12 +129,14 @@ internal sealed class ExtendedWindow<TWindow>
|
||||
|
||||
appWindow.Show(true);
|
||||
|
||||
bool micaApplied = new SystemBackdrop(window).TryApply();
|
||||
systemBackdrop = new(window);
|
||||
bool micaApplied = systemBackdrop.TryApply();
|
||||
logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed");
|
||||
|
||||
bool subClassApplied = subclassManager.TrySetWindowSubclass();
|
||||
logger.LogInformation(EventIds.SubClassing, "Apply {name} : {result}", nameof(WindowSubclassManager<TWindow>), subClassApplied ? "succeed" : "failed");
|
||||
|
||||
Ioc.Default.GetRequiredService<IMessenger>().Register(this);
|
||||
window.Closed += OnWindowClosed;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.System;
|
||||
using WinRT;
|
||||
@@ -18,7 +22,7 @@ public class SystemBackdrop
|
||||
private readonly Window window;
|
||||
|
||||
private DispatcherQueueHelper? dispatcherQueueHelper;
|
||||
private MicaController? backdropController;
|
||||
private ISystemBackdropControllerWithTargets? backdropController;
|
||||
private SystemBackdropConfiguration? configuration;
|
||||
|
||||
/// <summary>
|
||||
@@ -28,38 +32,68 @@ public class SystemBackdrop
|
||||
public SystemBackdrop(Window window)
|
||||
{
|
||||
this.window = window;
|
||||
using (IServiceScope scope = Ioc.Default.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
SettingEntry entry = appDbContext.Settings.SingleOrAdd(SettingEntry.SystemBackdropType, BackdropType.Mica.ToString());
|
||||
BackdropType = Enum.Parse<BackdropType>(entry.Value!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型
|
||||
/// </summary>
|
||||
public BackdropType BackdropType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 尝试设置背景
|
||||
/// </summary>
|
||||
/// <returns>是否设置成功</returns>
|
||||
public bool TryApply()
|
||||
{
|
||||
if (!MicaController.IsSupported())
|
||||
bool isSupport = BackdropType switch
|
||||
{
|
||||
BackdropType.Acrylic => DesktopAcrylicController.IsSupported(),
|
||||
BackdropType.Mica or BackdropType.MicaAlt => MicaController.IsSupported(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!isSupport)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcherQueueHelper = new();
|
||||
dispatcherQueueHelper.Ensure();
|
||||
// Previous one
|
||||
if (backdropController != null)
|
||||
{
|
||||
backdropController.RemoveAllSystemBackdropTargets();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcherQueueHelper = new();
|
||||
dispatcherQueueHelper.Ensure();
|
||||
}
|
||||
|
||||
// Hooking up the policy object
|
||||
configuration = new();
|
||||
configuration = new()
|
||||
{
|
||||
IsInputActive = true, // Initial configuration state.
|
||||
};
|
||||
SetConfigurationSourceTheme(configuration);
|
||||
|
||||
window.Activated += OnWindowActivated;
|
||||
window.Closed += OnWindowClosed;
|
||||
((FrameworkElement)window.Content).ActualThemeChanged += OnWindowThemeChanged;
|
||||
|
||||
// Initial configuration state.
|
||||
configuration.IsInputActive = true;
|
||||
SetConfigurationSourceTheme(configuration);
|
||||
|
||||
backdropController = new()
|
||||
backdropController = BackdropType switch
|
||||
{
|
||||
// Mica Alt
|
||||
Kind = MicaKind.BaseAlt,
|
||||
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);
|
||||
|
||||
@@ -69,7 +103,7 @@ public class SystemBackdrop
|
||||
|
||||
private void OnWindowActivated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
Must.NotNull(configuration!).IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
|
||||
configuration!.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
|
||||
}
|
||||
|
||||
private void OnWindowClosed(object sender, WindowEventArgs args)
|
||||
|
||||
@@ -52,9 +52,10 @@ internal class WindowSubclassManager<TWindow> : IDisposable
|
||||
|
||||
bool titleBarHooked = true;
|
||||
|
||||
// only hook up drag bar proc when use legacy Window.ExtendsContentIntoTitleBar
|
||||
// only hook up drag bar proc when not use legacy Window.ExtendsContentIntoTitleBar
|
||||
if (isLegacyDragBar)
|
||||
{
|
||||
titleBarHooked = false;
|
||||
hwndDragBar = FindWindowEx(hwnd, default, "DRAG_BAR_WINDOW_CLASS", string.Empty);
|
||||
|
||||
if (!hwndDragBar.IsNull)
|
||||
@@ -90,6 +91,12 @@ internal class WindowSubclassManager<TWindow> : IDisposable
|
||||
window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_NCRBUTTONDOWN:
|
||||
case WM_NCRBUTTONUP:
|
||||
{
|
||||
return new(0);
|
||||
}
|
||||
}
|
||||
|
||||
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Collection{T}"/> 部分
|
||||
/// </summary>
|
||||
public static partial class EnumerableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 移除集合中满足条件的项
|
||||
/// </summary>
|
||||
/// <typeparam name="T">集合项类型</typeparam>
|
||||
/// <param name="collection">集合</param>
|
||||
/// <param name="shouldRemovePredicate">是否应当移除</param>
|
||||
/// <returns>移除的个数</returns>
|
||||
public static int RemoveWhere<T>(this Collection<T> collection, Func<T, bool> shouldRemovePredicate)
|
||||
{
|
||||
int count = 0;
|
||||
foreach (T item in collection.ToList())
|
||||
{
|
||||
if (shouldRemovePredicate.Invoke(item))
|
||||
{
|
||||
collection.Remove(item);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Dictionary{TKey, TValue}"/> 部分
|
||||
/// </summary>
|
||||
public static partial class EnumerableExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取值或默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
/// <param name="dictionary">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>结果值</returns>
|
||||
public static TValue? GetValueOrDefault2<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue? defaultValue = default)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out TValue? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">增加的值</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key, int value)
|
||||
where TKey : notnull
|
||||
{
|
||||
// ref the value, so that we can manipulate it outside the dict.
|
||||
ref int current = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
current += value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns>是否存在键值</returns>
|
||||
public static bool TryIncrease<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
ref int value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
|
||||
if (!Unsafe.IsNullRef(ref value))
|
||||
{
|
||||
++value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="List{T}"/> 部分
|
||||
/// </summary>
|
||||
public static partial class EnumerableExtension
|
||||
{
|
||||
/// <inheritdoc cref="Enumerable.Average(IEnumerable{int})"/>
|
||||
public static double AverageNoThrow(this List<int> source)
|
||||
{
|
||||
Span<int> span = CollectionsMarshal.AsSpan(source);
|
||||
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long sum = 0;
|
||||
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
sum += span[i];
|
||||
}
|
||||
|
||||
return (double)sum / span.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果传入列表不为空则原路返回,
|
||||
/// 如果传入列表为空返回一个空的列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>源列表或空列表</returns>
|
||||
public static List<TSource> EmptyIfNull<TSource>(this List<TSource>? source)
|
||||
{
|
||||
return source ?? new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除表中首个满足条件的项
|
||||
/// </summary>
|
||||
/// <typeparam name="T">项的类型</typeparam>
|
||||
/// <param name="list">表</param>
|
||||
/// <param name="shouldRemovePredicate">是否应当移除</param>
|
||||
/// <returns>是否移除了元素</returns>
|
||||
public static bool RemoveFirstWhere<T>(this IList<T> list, Func<T, bool> shouldRemovePredicate)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (shouldRemovePredicate.Invoke(list[i]))
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
@@ -11,26 +8,6 @@ namespace Snap.Hutao.Extension;
|
||||
/// </summary>
|
||||
public static partial class EnumerableExtension
|
||||
{
|
||||
/// <inheritdoc cref="Enumerable.Average(IEnumerable{int})"/>
|
||||
public static double AverageNoThrow(this List<int> source)
|
||||
{
|
||||
Span<int> span = CollectionsMarshal.AsSpan(source);
|
||||
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long sum = 0;
|
||||
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
sum += span[i];
|
||||
}
|
||||
|
||||
return (double)sum / span.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计数
|
||||
/// </summary>
|
||||
@@ -63,18 +40,6 @@ public static partial class EnumerableExtension
|
||||
return source ?? Enumerable.Empty<TSource>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果传入列表不为空则原路返回,
|
||||
/// 如果传入列表为空返回一个空的列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <param name="source">源</param>
|
||||
/// <returns>源列表或空列表</returns>
|
||||
public static List<TSource> EmptyIfNull<TSource>(this List<TSource>? source)
|
||||
{
|
||||
return source ?? new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将源转换为仅包含单个元素的枚举
|
||||
/// </summary>
|
||||
@@ -99,94 +64,6 @@ public static partial class EnumerableExtension
|
||||
return source.FirstOrDefault(predicate) ?? source.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取值或默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
/// <param name="dictionary">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>结果值</returns>
|
||||
public static TValue? GetValueOrDefault2<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue? defaultValue = default)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out TValue? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">增加的值</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key, int value)
|
||||
where TKey : notnull
|
||||
{
|
||||
// ref the value, so that we can manipulate it outside the dict.
|
||||
ref int current = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
current += value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns>是否存在键值</returns>
|
||||
public static bool TryIncrease<TKey>(this Dictionary<TKey, int> dict, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
ref int value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
|
||||
if (!Unsafe.IsNullRef(ref value))
|
||||
{
|
||||
++value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除表中首个满足条件的项
|
||||
/// </summary>
|
||||
/// <typeparam name="T">项的类型</typeparam>
|
||||
/// <param name="list">表</param>
|
||||
/// <param name="shouldRemovePredicate">是否应当移除</param>
|
||||
/// <returns>是否移除了元素</returns>
|
||||
public static bool RemoveFirstWhere<T>(this IList<T> list, Func<T, bool> shouldRemovePredicate)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (shouldRemovePredicate.Invoke(list[i]))
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Enumerable.ToDictionary{TSource, TKey}(IEnumerable{TSource}, Func{TSource, TKey})"/>
|
||||
public static Dictionary<TKey, TSource> ToDictionaryOverride<TKey, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
|
||||
where TKey : notnull
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="StringBuilder"/> 扩展方法
|
||||
/// </summary>
|
||||
public static class StringBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 当条件符合时执行 <see cref="StringBuilder.Append(string?)"/>
|
||||
/// </summary>
|
||||
/// <param name="sb">字符串建造器</param>
|
||||
/// <param name="condition">条件</param>
|
||||
/// <param name="value">附加的字符串</param>
|
||||
/// <returns>同一个字符串建造器</returns>
|
||||
public static StringBuilder AppendIf(this StringBuilder sb, bool condition, string? value)
|
||||
{
|
||||
return condition ? sb.Append(value) : sb;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ public sealed partial class LaunchGameWindow : Window, IDisposable, IExtendedWin
|
||||
|
||||
scope = scopeFactory.CreateScope();
|
||||
RootGrid.DataContext = scope.ServiceProvider.GetRequiredService<LaunchGameViewModel>();
|
||||
Closed += (s, e) => Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
|
||||
namespace Snap.Hutao.Message;
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型改变消息
|
||||
/// </summary>
|
||||
internal class BackdropTypeChangedMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的背景类型改变消息
|
||||
/// </summary>
|
||||
/// <param name="backdropType">背景类型</param>
|
||||
public BackdropTypeChangedMessage(BackdropType backdropType)
|
||||
{
|
||||
BackdropType = backdropType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型
|
||||
/// </summary>
|
||||
public BackdropType BackdropType { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
|
||||
namespace Snap.Hutao.Message;
|
||||
|
||||
|
||||
282
src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.Designer.cs
generated
Normal file
282
src/Snap.Hutao/Snap.Hutao/Migrations/20221108081525_DailyNoteEntry.Designer.cs
generated
Normal file
@@ -0,0 +1,282 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Context.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20221108081525_DailyNoteEntry")]
|
||||
partial class DailyNoteEntry
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Cookie")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
public partial class DailyNoteEntry : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "daily_notes",
|
||||
columns: table => new
|
||||
{
|
||||
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
Uid = table.Column<string>(type: "TEXT", nullable: false),
|
||||
DailyNote = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ResinNotifyThreshold = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ResinNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
HomeCoinNotifyThreshold = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
HomeCoinNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
TransformerNotify = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
TransformerNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
DailyTaskNotify = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
DailyTaskNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ExpeditionNotify = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ExpeditionNotifySuppressed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ShowInHomeWidget = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_daily_notes", x => x.InnerId);
|
||||
table.ForeignKey(
|
||||
name: "FK_daily_notes_users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "users",
|
||||
principalColumn: "InnerId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_daily_notes_UserId",
|
||||
table: "daily_notes",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "daily_notes");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,62 @@ namespace Snap.Hutao.Migrations
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowInHomeWidget")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
@@ -197,6 +253,17 @@ namespace Snap.Hutao.Migrations
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
|
||||
@@ -18,11 +18,13 @@ public class LaunchScheme
|
||||
/// <param name="channel">通道</param>
|
||||
/// <param name="cps">通道描述字符串</param>
|
||||
/// <param name="subChannel">子通道</param>
|
||||
public LaunchScheme(string name, string channel, string subChannel)
|
||||
/// <param name="launcherId">启动器Id</param>
|
||||
public LaunchScheme(string name, string channel, string subChannel, string launcherId)
|
||||
{
|
||||
Name = name;
|
||||
Channel = channel;
|
||||
SubChannel = subChannel;
|
||||
LauncherId = launcherId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,4 +41,9 @@ public class LaunchScheme
|
||||
/// 子通道
|
||||
/// </summary>
|
||||
public string SubChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动器Id
|
||||
/// </summary>
|
||||
public string LauncherId { get; set; }
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using EntityUser = Snap.Hutao.Model.Entity.User;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding;
|
||||
namespace Snap.Hutao.Model.Binding.User;
|
||||
|
||||
/// <summary>
|
||||
/// 用于视图绑定的用户
|
||||
34
src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs
Normal file
34
src/Snap.Hutao/Snap.Hutao/Model/Binding/User/UserAndRole.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using EntityUser = Snap.Hutao.Model.Entity.User;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.User;
|
||||
|
||||
/// <summary>
|
||||
/// 角色与实体用户
|
||||
/// </summary>
|
||||
public class UserAndRole
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的实体用户与角色
|
||||
/// </summary>
|
||||
/// <param name="user">实体用户</param>
|
||||
/// <param name="role">角色</param>
|
||||
public UserAndRole(EntityUser user, UserGameRole role)
|
||||
{
|
||||
User = user;
|
||||
Role = role;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体用户
|
||||
/// </summary>
|
||||
public EntityUser User { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public UserGameRole Role { get; private set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺入口配置
|
||||
/// </summary>
|
||||
internal class DailyNoteEntryConfiguration : IEntityTypeConfiguration<DailyNoteEntry>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<DailyNoteEntry> builder)
|
||||
{
|
||||
builder.Property(e => e.DailyNote)
|
||||
.HasColumnType("TEXT")
|
||||
.HasConversion<JsonTextValueConverter<Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote>>();
|
||||
}
|
||||
}
|
||||
98
src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs
Normal file
98
src/Snap.Hutao/Snap.Hutao/Model/Entity/DailyNoteEntry.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺入口
|
||||
/// </summary>
|
||||
[Table("daily_notes")]
|
||||
public class DailyNoteEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
/// </summary>
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid InnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户Id
|
||||
/// </summary>
|
||||
[ForeignKey(nameof(UserId))]
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户
|
||||
/// </summary>
|
||||
public User User { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Uid
|
||||
/// </summary>
|
||||
public string Uid { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Json!!! 实时便笺
|
||||
/// </summary>
|
||||
public DailyNote? DailyNote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 树脂提醒阈值
|
||||
/// </summary>
|
||||
public int ResinNotifyThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断树脂是否继续提醒
|
||||
/// </summary>
|
||||
public bool ResinNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 洞天宝钱提醒阈值
|
||||
/// </summary>
|
||||
public int HomeCoinNotifyThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断洞天宝钱是否继续提醒
|
||||
/// </summary>
|
||||
public bool HomeCoinNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参量质变仪提醒
|
||||
/// </summary>
|
||||
public bool TransformerNotify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断参量质变仪是否继续提醒
|
||||
/// </summary>
|
||||
public bool TransformerNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每日委托提醒
|
||||
/// </summary>
|
||||
public bool DailyTaskNotify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断每日委托是否继续提醒
|
||||
/// </summary>
|
||||
public bool DailyTaskNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 探索派遣提醒
|
||||
/// </summary>
|
||||
public bool ExpeditionNotify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于判断探索派遣是否继续提醒
|
||||
/// </summary>
|
||||
public bool ExpeditionNotifySuppressed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否在主页显示小组件
|
||||
/// </summary>
|
||||
public bool ShowInHomeWidget { get; set; }
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public class GachaItem
|
||||
/// <summary>
|
||||
/// 存档
|
||||
/// </summary>
|
||||
[ForeignKey(nameof(ArchiveId))]
|
||||
public GachaArchive Archive { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -22,6 +22,11 @@ public class SettingEntry
|
||||
/// </summary>
|
||||
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
|
||||
|
||||
/// <summary>
|
||||
/// 窗口背景类型
|
||||
/// </summary>
|
||||
public const string SystemBackdropType = "SystemBackdropType";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 全屏
|
||||
/// </summary>
|
||||
|
||||
@@ -43,11 +43,6 @@ public class Achievement
|
||||
/// </summary>
|
||||
public int Progress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发器
|
||||
/// </summary>
|
||||
public IEnumerable<AchievementTrigger>? Triggers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就触发器
|
||||
/// </summary>
|
||||
public class AchievementTrigger
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发器类型
|
||||
/// </summary>
|
||||
public AchievementTriggerType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public string Id { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
public string Title { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
|
||||
namespace Snap.Hutao.Service.DailyNote;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺服务
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal class DailyNoteService
|
||||
{
|
||||
private readonly AppDbContext appDbContext;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的实时便笺服务
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
public DailyNoteService(AppDbContext appDbContext)
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
}
|
||||
|
||||
public async ValueTask RefreshDailyNotesAndNotifyAsync()
|
||||
{
|
||||
GameRecordClient gameRecordClient = Ioc.Default.GetRequiredService<GameRecordClient>();
|
||||
|
||||
foreach (Model.Entity.DailyNoteEntry entry in appDbContext.DailyNotes.Include(n => n.User))
|
||||
{
|
||||
entry.DailyNote = await gameRecordClient.GetDialyNoteAsync(entry.User, entry.Uid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
private async ValueTask NotifyDailyNoteAsync()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ internal class GachaLogUrlStokenProvider : IGachaLogUrlProvider
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<bool, string>> GetQueryAsync()
|
||||
{
|
||||
Model.Binding.User? user = userService.Current;
|
||||
Model.Binding.User.User? user = userService.Current;
|
||||
if (user != null && user.SelectedUserGameRole != null)
|
||||
{
|
||||
if (user.Cookie!.ContainsSToken())
|
||||
|
||||
@@ -168,25 +168,38 @@ internal class GameService : IGameService
|
||||
elements = IniSerializer.Deserialize(readStream).ToList();
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
|
||||
foreach (IniElement element in elements)
|
||||
{
|
||||
if (element is IniParameter parameter)
|
||||
{
|
||||
if (parameter.Key == "channel")
|
||||
{
|
||||
parameter.Value = scheme.Channel;
|
||||
if (parameter.Value != scheme.Channel)
|
||||
{
|
||||
parameter.Value = scheme.Channel;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (parameter.Key == "sub_channel")
|
||||
{
|
||||
parameter.Value = scheme.SubChannel;
|
||||
if (parameter.Value != scheme.SubChannel)
|
||||
{
|
||||
parameter.Value = scheme.SubChannel;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream writeStream = File.Create(configPath))
|
||||
if (changed)
|
||||
{
|
||||
IniSerializer.Serialize(writeStream, elements);
|
||||
using (FileStream writeStream = File.Create(configPath))
|
||||
{
|
||||
IniSerializer.Serialize(writeStream, elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +353,7 @@ internal class GameService : IGameService
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
gameAccount.UpdateName(name);
|
||||
|
||||
// sync database
|
||||
|
||||
@@ -44,7 +44,7 @@ internal class ManualGameLocator : IGameLocator
|
||||
{
|
||||
FileOpenPicker picker = pickerFactory.GetFileOpenPicker();
|
||||
picker.FileTypeFilter.Add(".exe");
|
||||
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
|
||||
picker.SuggestedStartLocation = PickerLocationId.Desktop;
|
||||
|
||||
// System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.
|
||||
// Not sure what's going on here.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Service;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User.User;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
@@ -18,6 +17,12 @@ public interface IUserService
|
||||
/// </summary>
|
||||
BindingUser? Current { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取角色与用户集合
|
||||
/// </summary>
|
||||
/// <returns>角色与用户集合</returns>
|
||||
Task<ObservableCollection<Model.Binding.User.UserAndRole>> GetRoleCollectionAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化用户服务及所有用户
|
||||
/// 异步获取同步的用户信息集合
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User.User;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User;
|
||||
using BindingUser = Snap.Hutao.Model.Binding.User.User;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
@@ -27,6 +26,7 @@ internal class UserService : IUserService
|
||||
|
||||
private BindingUser? currentUser;
|
||||
private ObservableCollection<BindingUser>? userCollection;
|
||||
private ObservableCollection<Model.Binding.User.UserAndRole>? roleCollection;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的用户服务
|
||||
@@ -86,15 +86,17 @@ internal class UserService : IUserService
|
||||
public async Task RemoveUserAsync(BindingUser user)
|
||||
{
|
||||
await Task.Yield();
|
||||
Must.NotNull(userCollection!);
|
||||
|
||||
// Sync cache
|
||||
userCollection.Remove(user);
|
||||
userCollection!.Remove(user);
|
||||
roleCollection!.RemoveWhere(r => r.User.InnerId == user.Entity.InnerId);
|
||||
|
||||
// Sync database
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// Note: cascade deleted dailynotes
|
||||
appDbContext.Users.RemoveAndSave(user.Entity);
|
||||
}
|
||||
}
|
||||
@@ -137,6 +139,27 @@ internal class UserService : IUserService
|
||||
return userCollection;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ObservableCollection<Model.Binding.User.UserAndRole>> GetRoleCollectionAsync()
|
||||
{
|
||||
if (roleCollection == null)
|
||||
{
|
||||
List<Model.Binding.User.UserAndRole> userAndRoles = new();
|
||||
ObservableCollection<BindingUser> observableUsers = await GetUserCollectionAsync().ConfigureAwait(false);
|
||||
foreach (BindingUser user in observableUsers.ToList())
|
||||
{
|
||||
foreach (UserGameRole role in user.UserGameRoles)
|
||||
{
|
||||
userAndRoles.Add(new(user.Entity, role));
|
||||
}
|
||||
}
|
||||
|
||||
roleCollection = new(userAndRoles);
|
||||
}
|
||||
|
||||
return roleCollection;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie)
|
||||
{
|
||||
@@ -148,7 +171,7 @@ internal class UserService : IUserService
|
||||
{
|
||||
// 检查 login ticket 是否存在
|
||||
// 若存在则尝试升级至 stoken
|
||||
await TryAddMultiTokenAsync(cookie, uid).ConfigureAwait(false);
|
||||
await cookie.TryAddMultiTokenAsync(uid).ConfigureAwait(false);
|
||||
|
||||
// 检查 uid 对应用户是否存在
|
||||
if (UserHelper.TryGetUserByUid(userCollection, uid, out BindingUser? userWithSameUid))
|
||||
@@ -180,32 +203,14 @@ internal class UserService : IUserService
|
||||
}
|
||||
else if (cookie.ContainsLTokenAndCookieToken())
|
||||
{
|
||||
return await TryCreateUserAndAddAsync(userCollection, cookie).ConfigureAwait(false);
|
||||
return await TryCreateUserAndAddAsync(cookie).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return new(UserOptionResult.Incomplete, null!);
|
||||
}
|
||||
|
||||
private async Task TryAddMultiTokenAsync(Cookie cookie, string uid)
|
||||
{
|
||||
if (cookie.TryGetLoginTicket(out string? loginTicket))
|
||||
{
|
||||
// get multitoken
|
||||
Dictionary<string, string> multiToken = await Ioc.Default
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicket, uid, default)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (multiToken.Count >= 2)
|
||||
{
|
||||
cookie.InsertMultiToken(uid, multiToken);
|
||||
cookie.RemoveLoginTicket();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(ObservableCollection<BindingUser> users, Cookie cookie)
|
||||
private async Task<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(Cookie cookie)
|
||||
{
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
@@ -217,8 +222,19 @@ internal class UserService : IUserService
|
||||
if (newUser != null)
|
||||
{
|
||||
// Sync cache
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
users.Add(newUser);
|
||||
if (userCollection != null)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
userCollection!.Add(newUser);
|
||||
|
||||
if (roleCollection != null)
|
||||
{
|
||||
foreach (UserGameRole role in newUser.UserGameRoles)
|
||||
{
|
||||
roleCollection.Add(new(newUser.Entity, role));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sync database
|
||||
appDbContext.Users.AddAndSave(newUser.Entity);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>embedded</DebugType>
|
||||
<ApplicationIcon>Assets\Logo.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -115,28 +116,31 @@
|
||||
<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="6.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
|
||||
<!-- The PrivateAssets & IncludeAssets of Microsoft.EntityFrameworkCore.Tools should be remove to prevent multiple deps files-->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.3.44">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.3.48">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.0.64" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.63-beta">
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.104-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25211-preview" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25231-preview" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.220930.4-preview2" />
|
||||
<PackageReference Include="MiniExcel" Version="1.28.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="TaskScheduler" Version="2.10.1" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
BorderThickness="{ThemeResource CardBorderThickness}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
Padding="8"
|
||||
Margin="0,16,0,0"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
MinHeight="48">
|
||||
|
||||
@@ -23,145 +23,144 @@
|
||||
</mxi:Interaction.Behaviors>
|
||||
<shc:ScopedPage.Resources>
|
||||
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
|
||||
</shc:ScopedPage.Resources>
|
||||
<Grid>
|
||||
<ScrollViewer Padding="0,0,4,0">
|
||||
<ItemsControl
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding Announcement.List}"
|
||||
Padding="0"
|
||||
Margin="16,16,0,-6">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="{Binding TypeLabel}"
|
||||
Margin="0,0,0,12"
|
||||
Style="{StaticResource TitleTextBlockStyle}"/>
|
||||
<cwucont:AdaptiveGridView
|
||||
|
||||
<DataTemplate x:Key="AnnouncementTemplate">
|
||||
<cwucont:AdaptiveGridView
|
||||
cwua:ItemsReorderAnimation.Duration="0:0:0.1"
|
||||
SelectionMode="None"
|
||||
DesiredWidth="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
|
||||
ItemsSource="{Binding List}"
|
||||
Margin="0,0,2,0">
|
||||
<cwucont:AdaptiveGridView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
cwu:UIElementExtensions.ClipToBounds="True">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!--Image Layer-->
|
||||
<Border cwu:UIElementExtensions.ClipToBounds="True">
|
||||
<Border
|
||||
VerticalAlignment="Top"
|
||||
cwu:VisualExtensions.NormalizedCenterPoint="0.5">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:AutoHeightBehavior TargetWidth="1080" TargetHeight="390"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<shci:CachedImage
|
||||
Stretch="UniformToFill"
|
||||
Source="{Binding Banner}"/>
|
||||
<cwua:Explicit.Animations>
|
||||
<cwua:AnimationSet x:Name="ImageZoomInAnimation">
|
||||
<shca:ImageZoomInAnimation/>
|
||||
</cwua:AnimationSet>
|
||||
<cwua:AnimationSet x:Name="ImageZoomOutAnimation">
|
||||
<shca:ImageZoomOutAnimation/>
|
||||
</cwua:AnimationSet>
|
||||
</cwua:Explicit.Animations>
|
||||
</Border>
|
||||
</Border>
|
||||
<!--Time Description-->
|
||||
<Grid Grid.Row="0">
|
||||
<Border
|
||||
Height="24"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Visibility="{Binding ShouldShowTimeDescription,Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ProgressBar
|
||||
MinHeight="2"
|
||||
Value="{Binding TimePercent,Mode=OneWay}"
|
||||
CornerRadius="0"
|
||||
Maximum="1"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="Transparent"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
<!--General Description-->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadiusBottom}">
|
||||
<StackPanel Margin="4" VerticalAlignment="Bottom">
|
||||
<TextBlock
|
||||
Margin="4,6,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Subtitle}"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="WordEllipsis"/>
|
||||
Margin="16,16,0,-4">
|
||||
<cwucont:AdaptiveGridView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
cwu:UIElementExtensions.ClipToBounds="True">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!--Image Layer-->
|
||||
<Border cwu:UIElementExtensions.ClipToBounds="True">
|
||||
<Border
|
||||
VerticalAlignment="Top"
|
||||
cwu:VisualExtensions.NormalizedCenterPoint="0.5">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:AutoHeightBehavior TargetWidth="1080" TargetHeight="390"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<shci:CachedImage
|
||||
Stretch="UniformToFill"
|
||||
Source="{Binding Banner}"/>
|
||||
<cwua:Explicit.Animations>
|
||||
<cwua:AnimationSet x:Name="ImageZoomInAnimation">
|
||||
<shca:ImageZoomInAnimation/>
|
||||
</cwua:AnimationSet>
|
||||
<cwua:AnimationSet x:Name="ImageZoomOutAnimation">
|
||||
<shca:ImageZoomOutAnimation/>
|
||||
</cwua:AnimationSet>
|
||||
</cwua:Explicit.Animations>
|
||||
</Border>
|
||||
</Border>
|
||||
<!--Time Description-->
|
||||
<Grid Grid.Row="0">
|
||||
<Border
|
||||
Height="24"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Visibility="{Binding ShouldShowTimeDescription,Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ProgressBar
|
||||
MinHeight="2"
|
||||
Value="{Binding TimePercent,Mode=OneWay}"
|
||||
CornerRadius="0"
|
||||
Maximum="1"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="Transparent"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
<!--General Description-->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
CornerRadius="{StaticResource CompatCornerRadiusBottom}">
|
||||
<StackPanel Margin="4" VerticalAlignment="Bottom">
|
||||
<TextBlock
|
||||
Margin="4,6,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Subtitle}"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="WordEllipsis"/>
|
||||
|
||||
<TextBlock
|
||||
Text="{Binding Title}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="WordEllipsis"
|
||||
Margin="4,6,0,0"
|
||||
Opacity="0.6"/>
|
||||
<TextBlock
|
||||
Text="{Binding Title}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="WordEllipsis"
|
||||
Margin="4,6,0,0"
|
||||
Opacity="0.6"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
FontSize="10"
|
||||
Opacity="0.4"
|
||||
Margin="4,4,0,4"
|
||||
Text="{Binding TimeFormatted}"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
FontSize="10"
|
||||
Opacity="0.8"
|
||||
Margin="4,4,4,4"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Visibility="{Binding ShouldShowTimeDescription,Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Text="{Binding TimeDescription}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
<mxi:Interaction.Behaviors>
|
||||
<mxic:EventTriggerBehavior EventName="Tapped">
|
||||
<mxic:InvokeCommandAction
|
||||
Command="{Binding DataContext.OpenAnnouncementUICommand,Source={StaticResource BindingProxy}}"
|
||||
CommandParameter="{Binding Content}"/>
|
||||
</mxic:EventTriggerBehavior>
|
||||
<mxic:EventTriggerBehavior EventName="PointerEntered">
|
||||
<cwub:StartAnimationAction Animation="{Binding ElementName=ImageZoomInAnimation}" />
|
||||
</mxic:EventTriggerBehavior>
|
||||
<mxic:EventTriggerBehavior EventName="PointerExited">
|
||||
<cwub:StartAnimationAction Animation="{Binding ElementName=ImageZoomOutAnimation}" />
|
||||
</mxic:EventTriggerBehavior>
|
||||
</mxi:Interaction.Behaviors>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</cwucont:AdaptiveGridView.ItemTemplate>
|
||||
</cwucont:AdaptiveGridView>
|
||||
</StackPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
FontSize="10"
|
||||
Opacity="0.4"
|
||||
Margin="4,4,0,4"
|
||||
Text="{Binding TimeFormatted}"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
FontSize="10"
|
||||
Opacity="0.8"
|
||||
Margin="4,4,4,4"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Visibility="{Binding ShouldShowTimeDescription,Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Text="{Binding TimeDescription}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
<mxi:Interaction.Behaviors>
|
||||
<mxic:EventTriggerBehavior EventName="Tapped">
|
||||
<mxic:InvokeCommandAction
|
||||
Command="{Binding DataContext.OpenAnnouncementUICommand,Source={StaticResource BindingProxy}}"
|
||||
CommandParameter="{Binding Content}"/>
|
||||
</mxic:EventTriggerBehavior>
|
||||
<mxic:EventTriggerBehavior EventName="PointerEntered">
|
||||
<cwub:StartAnimationAction Animation="{Binding ElementName=ImageZoomInAnimation}" />
|
||||
</mxic:EventTriggerBehavior>
|
||||
<mxic:EventTriggerBehavior EventName="PointerExited">
|
||||
<cwub:StartAnimationAction Animation="{Binding ElementName=ImageZoomOutAnimation}" />
|
||||
</mxic:EventTriggerBehavior>
|
||||
</mxi:Interaction.Behaviors>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</cwucont:AdaptiveGridView.ItemTemplate>
|
||||
</cwucont:AdaptiveGridView>
|
||||
</DataTemplate>
|
||||
</shc:ScopedPage.Resources>
|
||||
<Grid>
|
||||
<ScrollViewer Padding="0,0,4,0">
|
||||
<StackPanel>
|
||||
<Pivot>
|
||||
<PivotItem
|
||||
Header="活动公告"
|
||||
Content="{Binding Announcement.List[0]}"
|
||||
ContentTemplate="{StaticResource AnnouncementTemplate}"/>
|
||||
<PivotItem
|
||||
Header="游戏公告"
|
||||
Content="{Binding Announcement.List[1]}"
|
||||
ContentTemplate="{StaticResource AnnouncementTemplate}"/>
|
||||
</Pivot>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</shc:ScopedPage>
|
||||
@@ -4,13 +4,26 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:sc="using:SettingsUI.Controls"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance shv:DailyNoteViewModel}"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
<shc:BindingProxy
|
||||
x:Key="ViewModelBindingProxy"
|
||||
DataContext="{Binding}"/>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
@@ -20,11 +33,62 @@
|
||||
<CommandBar
|
||||
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
|
||||
DefaultLabelPosition="Right">
|
||||
<AppBarButton Label="添加角色" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<AppBarButton Label="立即刷新" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<AppBarButton Label="添加角色" Icon="{shcm:FontIcon Glyph=}">
|
||||
<AppBarButton.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style
|
||||
TargetType="FlyoutPresenter"
|
||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}">
|
||||
<Setter Property="Padding" Value="0,2,0,2"/>
|
||||
<Setter Property="Background" Value="{ThemeResource FlyoutPresenterBackground}" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Margin="16,12,16,16"
|
||||
Text="添加角色以定时刷新"
|
||||
Style="{StaticResource BaseTextBlockStyle}"/>
|
||||
<ScrollViewer MaxHeight="320" Padding="16,0">
|
||||
<ItemsControl ItemsSource="{Binding UserAndRoles}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Padding="0,0,0,16">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Role.Nickname}"/>
|
||||
<TextBlock
|
||||
Margin="0,2,0,0"
|
||||
Opacity="0.6"
|
||||
Text="{Binding Role.Description}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
Content=""
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
BorderBrush="{x:Null}"
|
||||
Margin="16,0,0,0"
|
||||
Padding="12"
|
||||
Command="{Binding DataContext.TrackRoleCommand,Source={StaticResource ViewModelBindingProxy}}"
|
||||
CommandParameter="{Binding}"
|
||||
ToolTipService.ToolTip="添加"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</AppBarButton.Flyout>
|
||||
</AppBarButton>
|
||||
<AppBarButton Label="通知设置" Icon="{shcm:FontIcon Glyph=}">
|
||||
<AppBarButton.Flyout>
|
||||
<Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight">
|
||||
<StackPanel>
|
||||
<RadioButtons ItemsSource="{Binding RefreshTimes}">
|
||||
<RadioButtons.Header>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<Rectangle
|
||||
Height="48"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{StaticResource CardBackgroundFillColorSecondary}"/>
|
||||
Fill="{StaticResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<Pivot Grid.RowSpan="2">
|
||||
<Pivot.LeftHeader>
|
||||
<ComboBox
|
||||
|
||||
@@ -23,10 +23,16 @@
|
||||
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
|
||||
|
||||
<Style TargetType="Button" BasedOn="{StaticResource SettingButtonStyle}">
|
||||
<Setter Property="MinWidth" Value="160"/>
|
||||
<Setter Property="MinWidth" Value="156"/>
|
||||
</Style>
|
||||
<Style TargetType="HyperlinkButton" BasedOn="{StaticResource HyperlinkButtonStyle}">
|
||||
<Setter Property="MinWidth" Value="160"/>
|
||||
<Setter Property="MinWidth" Value="156"/>
|
||||
</Style>
|
||||
<Style TargetType="ComboBox" BasedOn="{StaticResource DefaultComboBoxStyle}">
|
||||
<Setter Property="MinWidth" Value="156"/>
|
||||
</Style>
|
||||
<Style TargetType="NumberBox">
|
||||
<Setter Property="MinWidth" Value="158"/>
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
<Grid>
|
||||
@@ -48,7 +54,6 @@
|
||||
Description="切换游戏服务器,B服用户需要自备额外的 PCGameSDK.dll 文件">
|
||||
<sc:Setting.ActionContent>
|
||||
<ComboBox
|
||||
Width="160"
|
||||
ItemsSource="{Binding KnownSchemes}"
|
||||
SelectedItem="{Binding SelectedScheme,Mode=TwoWay}"
|
||||
DisplayMemberPath="Name"/>
|
||||
@@ -56,30 +61,20 @@
|
||||
</sc:Setting>
|
||||
<sc:SettingExpander IsExpanded="True">
|
||||
<sc:SettingExpander.Header>
|
||||
<Grid Padding="0,16">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon Glyph=""/>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
Margin="20,0,0,0"
|
||||
Text="账号"/>
|
||||
<TextBlock
|
||||
Opacity="0.8"
|
||||
Margin="20,0,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="在游戏内切换账号,网络环境发生变化后需要重新手动检测"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding DetectGameAccountCommand}"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,8,0"
|
||||
Width="128"
|
||||
MinWidth="128"
|
||||
Content="检测"/>
|
||||
</Grid>
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="账号"
|
||||
Description="在游戏内切换账号,网络环境发生变化后需要重新手动检测">
|
||||
<sc:Setting.ActionContent>
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding DetectGameAccountCommand}"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,8,0"
|
||||
MinWidth="124"
|
||||
Content="检测"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
</sc:SettingExpander.Header>
|
||||
<ListView
|
||||
ItemsSource="{Binding GameAccounts}"
|
||||
@@ -118,7 +113,7 @@
|
||||
CommandParameter="{Binding}"
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"/>
|
||||
<Button
|
||||
Margin="4,8"
|
||||
Margin="4,8,0,8"
|
||||
MinWidth="48"
|
||||
VerticalAlignment="Stretch"
|
||||
ToolTipService.ToolTip="删除"
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
<Style TargetType="HyperlinkButton" BasedOn="{StaticResource HyperlinkButtonStyle}">
|
||||
<Setter Property="MinWidth" Value="160"/>
|
||||
</Style>
|
||||
<Style TargetType="ComboBox" BasedOn="{StaticResource DefaultComboBoxStyle}">
|
||||
<Setter Property="MinWidth" Value="160"/>
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
<ScrollViewer>
|
||||
<Grid>
|
||||
@@ -90,6 +93,19 @@
|
||||
</sc:SettingExpander>
|
||||
</sc:SettingsGroup>
|
||||
|
||||
<sc:SettingsGroup Header="外观">
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="背景材质"
|
||||
Description="更改窗体的背景材质">
|
||||
<ComboBox
|
||||
SelectedItem="{Binding SelectedBackdropType,Mode=TwoWay}"
|
||||
ItemsSource="{Binding BackdropTypes}"
|
||||
DisplayMemberPath="Name"/>
|
||||
</sc:Setting>
|
||||
|
||||
</sc:SettingsGroup>
|
||||
|
||||
<sc:SettingsGroup Header="祈愿记录">
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
|
||||
@@ -198,7 +198,6 @@
|
||||
</mxic:EventTriggerBehavior>
|
||||
</mxi:Interaction.Behaviors>
|
||||
</Grid>
|
||||
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
@@ -6,6 +6,7 @@ using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.AvatarInfo;
|
||||
using Snap.Hutao.Service.User;
|
||||
@@ -79,7 +80,7 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
private Task OpenUIAsync()
|
||||
{
|
||||
if (userService.Current is Model.Binding.User user)
|
||||
if (userService.Current is User user)
|
||||
{
|
||||
if (user.SelectedUserGameRole is UserGameRole role)
|
||||
{
|
||||
@@ -92,7 +93,7 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
private Task RefreshByUserGameRoleAsync()
|
||||
{
|
||||
if (userService.Current is Model.Binding.User user)
|
||||
if (userService.Current is User user)
|
||||
{
|
||||
if (user.SelectedUserGameRole is UserGameRole role)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
using Snap.Hutao.Service.User;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
@@ -11,8 +15,10 @@ namespace Snap.Hutao.ViewModel;
|
||||
/// 实时便笺视图模型
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
|
||||
internal class DailyNoteViewModel : ObservableObject, ISupportCancellation, IDisposable
|
||||
{
|
||||
private readonly IUserService userService;
|
||||
|
||||
private readonly List<NamedValue<int>> refreshTimes = new()
|
||||
{
|
||||
new("4 分钟 | 0.5 树脂", 240),
|
||||
@@ -22,6 +28,23 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
|
||||
new("60 分钟 | 7.5 树脂", 3600),
|
||||
};
|
||||
|
||||
private bool isReminderNotification;
|
||||
private NamedValue<int>? selectedRefreshTime;
|
||||
private ObservableCollection<UserAndRole>? userAndRoles;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的实时便笺视图模型
|
||||
/// </summary>
|
||||
/// <param name="userService">用户服务</param>
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
public DailyNoteViewModel(IUserService userService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
|
||||
{
|
||||
this.userService = userService;
|
||||
|
||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||
TrackRoleCommand = asyncRelayCommandFactory.Create(TrackRoleAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
@@ -29,4 +52,44 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
|
||||
/// 刷新时间
|
||||
/// </summary>
|
||||
public List<NamedValue<int>> RefreshTimes { get => refreshTimes; }
|
||||
|
||||
/// <summary>
|
||||
/// 选中的刷新时间
|
||||
/// </summary>
|
||||
public NamedValue<int>? SelectedRefreshTime { get => selectedRefreshTime; set => SetProperty(ref selectedRefreshTime, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 提醒式通知
|
||||
/// </summary>
|
||||
public bool IsReminderNotification { get => isReminderNotification; set => SetProperty(ref isReminderNotification, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 用户与角色集合
|
||||
/// </summary>
|
||||
public ObservableCollection<UserAndRole>? UserAndRoles { get => userAndRoles; set => userAndRoles = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 打开界面命令
|
||||
/// </summary>
|
||||
public ICommand OpenUICommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 跟踪角色命令
|
||||
/// </summary>
|
||||
public ICommand TrackRoleCommand { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
UserAndRoles = await userService.GetRoleCollectionAsync().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async Task TrackRoleAsync()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
|
||||
IUserService userService = Ioc.Default.GetRequiredService<IUserService>();
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
|
||||
if (userService.Current is Model.Binding.User user)
|
||||
if (userService.Current is Model.Binding.User.User user)
|
||||
{
|
||||
if (user.SelectedUserGameRole == null)
|
||||
{
|
||||
|
||||
@@ -35,8 +35,8 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
|
||||
private readonly List<LaunchScheme> knownSchemes = new()
|
||||
{
|
||||
new LaunchScheme(name: "官方服 | 天空岛", channel: "1", subChannel: "1"),
|
||||
new LaunchScheme(name: "渠道服 | 世界树", channel: "14", subChannel: "0"),
|
||||
new LaunchScheme(name: "官方服 | 天空岛", channel: "1", subChannel: "1", launcherId: "18"),
|
||||
new LaunchScheme(name: "渠道服 | 世界树", channel: "14", subChannel: "0", launcherId: "17"),
|
||||
|
||||
// new LaunchScheme(name: "国际服 | 暂不支持", channel: "1", subChannel: "0"),
|
||||
};
|
||||
@@ -231,7 +231,7 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
infoBarService.Warning("切换服务器失败,保存配置文件时发生异常\n请以管理员模式启动胡桃。");
|
||||
infoBarService.Warning("读取或保存配置文件时发生异常,请以管理员模式启动胡桃。");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
if (!gameService.SetGameAccount(SelectedGameAccount))
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IInfoBarService>().Warning("切换账号失败");
|
||||
infoBarService.Warning("切换账号失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Game.Locator;
|
||||
@@ -22,9 +24,17 @@ internal class SettingViewModel : ObservableObject
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly IGameService gameService;
|
||||
private readonly SettingEntry isEmptyHistoryWishVisibleEntry;
|
||||
private readonly SettingEntry selectedBackdropTypeEntry;
|
||||
private readonly List<NamedValue<BackdropType>> backdropTypes = new()
|
||||
{
|
||||
new("亚克力", BackdropType.Acrylic),
|
||||
new("云母", BackdropType.Mica),
|
||||
new("变种云母", BackdropType.MicaAlt),
|
||||
};
|
||||
|
||||
private bool isEmptyHistoryWishVisible;
|
||||
private string gamePath;
|
||||
private NamedValue<BackdropType> selectedBackdropType;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的测试视图模型
|
||||
@@ -40,10 +50,16 @@ internal class SettingViewModel : ObservableObject
|
||||
|
||||
Experimental = experimental;
|
||||
|
||||
isEmptyHistoryWishVisibleEntry = appDbContext.Settings
|
||||
.SingleOrAdd(e => e.Key == SettingEntry.IsEmptyHistoryWishVisible, () => new(SettingEntry.IsEmptyHistoryWishVisible, true.ToString()), out _);
|
||||
isEmptyHistoryWishVisibleEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.IsEmptyHistoryWishVisible, true.ToString());
|
||||
IsEmptyHistoryWishVisible = bool.Parse(isEmptyHistoryWishVisibleEntry.Value!);
|
||||
|
||||
selectedBackdropTypeEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.SystemBackdropType, BackdropType.Mica.ToString());
|
||||
BackdropType type = Enum.Parse<BackdropType>(selectedBackdropTypeEntry.Value!);
|
||||
|
||||
// prevent unnecessary backdrop setting.
|
||||
selectedBackdropType = backdropTypes.Single(t => t.Value == type);
|
||||
OnPropertyChanged(nameof(SelectedBackdropType));
|
||||
|
||||
GamePath = gameService.GetGamePathSkipLocator();
|
||||
|
||||
SetGamePathCommand = asyncRelayCommandFactory.Create(SetGamePathAsync);
|
||||
@@ -67,9 +83,11 @@ internal class SettingViewModel : ObservableObject
|
||||
get => isEmptyHistoryWishVisible;
|
||||
set
|
||||
{
|
||||
SetProperty(ref isEmptyHistoryWishVisible, value);
|
||||
isEmptyHistoryWishVisibleEntry.Value = value.ToString();
|
||||
appDbContext.Settings.UpdateAndSave(isEmptyHistoryWishVisibleEntry);
|
||||
if (SetProperty(ref isEmptyHistoryWishVisible, value))
|
||||
{
|
||||
isEmptyHistoryWishVisibleEntry.Value = value.ToString();
|
||||
appDbContext.Settings.UpdateAndSave(isEmptyHistoryWishVisibleEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +101,29 @@ internal class SettingViewModel : ObservableObject
|
||||
set => SetProperty(ref gamePath, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 背景类型
|
||||
/// </summary>
|
||||
public List<NamedValue<BackdropType>> BackdropTypes { get => backdropTypes; }
|
||||
|
||||
/// <summary>
|
||||
/// 选中的背景类型
|
||||
/// </summary>
|
||||
public NamedValue<BackdropType> SelectedBackdropType
|
||||
{
|
||||
get => selectedBackdropType;
|
||||
[MemberNotNull(nameof(selectedBackdropType))]
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedBackdropType, value))
|
||||
{
|
||||
selectedBackdropTypeEntry.Value = value.Value.ToString();
|
||||
appDbContext.Settings.UpdateAndSave(selectedBackdropTypeEntry);
|
||||
Ioc.Default.GetRequiredService<IMessenger>().Send(new Message.BackdropTypeChangedMessage(value.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实验性功能
|
||||
/// </summary>
|
||||
|
||||
@@ -6,7 +6,7 @@ using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.User;
|
||||
|
||||
@@ -43,6 +43,17 @@ internal static class ApiEndpoints
|
||||
/// </summary>
|
||||
public const string GameRecordCharacter = $"{ApiTakumiRecordApi}/character";
|
||||
|
||||
/// <summary>
|
||||
/// 游戏记录实时便笺
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="server">服务器区域</param>
|
||||
/// <returns>游戏记录实时便笺字符串</returns>
|
||||
public static string GameRecordDailyNote(string uid, string server)
|
||||
{
|
||||
return $"{ApiTakumiRecordApi}/dailyNote?server={server}&role_id={uid}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 游戏记录主页
|
||||
/// </summary>
|
||||
@@ -121,12 +132,13 @@ internal static class ApiEndpoints
|
||||
/// <summary>
|
||||
/// 启动器资源
|
||||
/// </summary>
|
||||
/// <param name="launcherId">启动器Id</param>
|
||||
/// <param name="channel">通道</param>
|
||||
/// <param name="subChannel">子通道</param>
|
||||
/// <returns>启动器资源字符串</returns>
|
||||
public static string SdkStaticLauncherResource(string channel, string subChannel)
|
||||
public static string SdkStaticLauncherResource(string launcherId, string channel, string subChannel)
|
||||
{
|
||||
return $"{SdkStaticLauncherApi}/resource?key=eYd89JmJ&launcher_id=18&channel_id={channel}&sub_channel_id={subChannel}";
|
||||
return $"{SdkStaticLauncherApi}/resource?key=eYd89JmJ&launcher_id={launcherId}&channel_id={channel}&sub_channel_id={subChannel}";
|
||||
}
|
||||
|
||||
// https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/content?filter_adv=true&key=eYd89JmJ&language=zh-cn&launcher_id=18
|
||||
|
||||
@@ -36,7 +36,7 @@ internal class UserClient
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>详细信息</returns>
|
||||
public async Task<UserInfo?> GetUserFullInfoAsync(Model.Binding.User user, CancellationToken token = default)
|
||||
public async Task<UserInfo?> GetUserFullInfoAsync(Model.Binding.User.User user, CancellationToken token = default)
|
||||
{
|
||||
Response<UserFullInfoWrapper>? resp = await httpClient
|
||||
.SetUser(user)
|
||||
|
||||
24
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs
Normal file
24
src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.Constant.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
/// <summary>
|
||||
/// 键部分
|
||||
/// </summary>
|
||||
[SuppressMessage("", "SA1310")]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public partial class Cookie
|
||||
{
|
||||
public const string COOKIE_TOKEN = "cookie_token";
|
||||
public const string ACCOUNT_ID = "account_id";
|
||||
|
||||
public const string LOGIN_TICKET = "login_ticket";
|
||||
public const string LOGIN_UID = "login_uid";
|
||||
|
||||
public const string LTOKEN = "ltoken";
|
||||
public const string LTUID = "ltuid";
|
||||
|
||||
public const string STOKEN = "stoken";
|
||||
public const string STUID = "stuid";
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
@@ -90,20 +91,6 @@ public partial class Cookie
|
||||
return inner.ContainsKey(STOKEN);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入Stoken
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="multiToken">tokens</param>
|
||||
public void InsertMultiToken(string uid, Dictionary<string, string> multiToken)
|
||||
{
|
||||
inner[STUID] = uid;
|
||||
inner[STOKEN] = multiToken[STOKEN];
|
||||
|
||||
inner[LTUID] = uid;
|
||||
inner[LTOKEN] = multiToken[LTOKEN];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入 Stoken
|
||||
/// </summary>
|
||||
@@ -115,15 +102,6 @@ public partial class Cookie
|
||||
inner[STOKEN] = cookie.inner[STOKEN];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除 LoginTicket
|
||||
/// </summary>
|
||||
public void RemoveLoginTicket()
|
||||
{
|
||||
inner.Remove(LOGIN_TICKET);
|
||||
inner.Remove(LOGIN_UID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除无效的键
|
||||
/// </summary>
|
||||
@@ -186,6 +164,34 @@ public partial class Cookie
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步尝试添加MultiToken
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <returns>任务</returns>
|
||||
public async Task TryAddMultiTokenAsync(string uid)
|
||||
{
|
||||
if (TryGetLoginTicket(out string? loginTicket))
|
||||
{
|
||||
// get multitoken
|
||||
Dictionary<string, string> multiToken = await Ioc.Default
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicket, uid, default)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (multiToken.Count >= 2)
|
||||
{
|
||||
inner[STUID] = uid;
|
||||
inner[STOKEN] = multiToken[STOKEN];
|
||||
inner[LTUID] = uid;
|
||||
inner[LTOKEN] = multiToken[LTOKEN];
|
||||
|
||||
inner.Remove(LOGIN_TICKET);
|
||||
inner.Remove(LOGIN_UID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Cookie的字符串表示
|
||||
/// </summary>
|
||||
@@ -195,23 +201,3 @@ public partial class Cookie
|
||||
return string.Join(';', inner.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 键部分
|
||||
/// </summary>
|
||||
[SuppressMessage("", "SA1310")]
|
||||
[SuppressMessage("", "SA1600")]
|
||||
public partial class Cookie
|
||||
{
|
||||
public const string COOKIE_TOKEN = "cookie_token";
|
||||
public const string ACCOUNT_ID = "account_id";
|
||||
|
||||
public const string LOGIN_TICKET = "login_ticket";
|
||||
public const string LOGIN_UID = "login_uid";
|
||||
|
||||
public const string LTOKEN = "ltoken";
|
||||
public const string LTUID = "ltuid";
|
||||
|
||||
public const string STOKEN = "stoken";
|
||||
public const string STUID = "stuid";
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
using Snap.Hutao.Web.Request;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
@@ -64,7 +64,7 @@ internal static class HttpClientExtensions
|
||||
/// 设置用户的Cookie
|
||||
/// </summary>
|
||||
/// <param name="httpClient">http客户端</param>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="user">绑定用户</param>
|
||||
/// <returns>客户端</returns>
|
||||
internal static HttpClient SetUser(this HttpClient httpClient, User user)
|
||||
{
|
||||
@@ -72,6 +72,18 @@ internal static class HttpClientExtensions
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置用户的Cookie
|
||||
/// </summary>
|
||||
/// <param name="httpClient">http客户端</param>
|
||||
/// <param name="user">实体用户</param>
|
||||
/// <returns>客户端</returns>
|
||||
internal static HttpClient SetUser(this HttpClient httpClient, Model.Entity.User user)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Set("Cookie", user.Cookie!.ToString());
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Referer
|
||||
/// </summary>
|
||||
|
||||
@@ -41,6 +41,11 @@ public struct PlayerUid
|
||||
get => region ??= EvaluateRegion(Value[0]);
|
||||
}
|
||||
|
||||
public static implicit operator PlayerUid(string source)
|
||||
{
|
||||
return new(source);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -39,8 +39,9 @@ internal class ResourceClient
|
||||
/// <returns>游戏资源</returns>
|
||||
public async Task<GameResource?> GetResourceAsync(LaunchScheme scheme, CancellationToken token = default)
|
||||
{
|
||||
string url = ApiEndpoints.SdkStaticLauncherResource(scheme.LauncherId, scheme.Channel, scheme.SubChannel);
|
||||
Response<GameResource>? response = await httpClient
|
||||
.TryCatchGetFromJsonAsync<Response<GameResource>>(ApiEndpoints.SdkStaticLauncherResource(scheme.Channel, scheme.SubChannel), options, logger, token)
|
||||
.TryCatchGetFromJsonAsync<Response<GameResource>>(url, options, logger, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return response?.Data;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Net.Http;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Net.Http;
|
||||
|
||||
@@ -64,7 +64,7 @@ public class UserGameRole
|
||||
get => $"{RegionName} | Lv.{Level}";
|
||||
}
|
||||
|
||||
public static explicit operator PlayerUid(UserGameRole userGameRole)
|
||||
public static implicit operator PlayerUid(UserGameRole userGameRole)
|
||||
{
|
||||
return new PlayerUid(userGameRole.GameUid, userGameRole.Region);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺
|
||||
/// </summary>
|
||||
public class DailyNote
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前树脂
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_resin")]
|
||||
public int CurrentResin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大树脂
|
||||
/// </summary>
|
||||
[JsonPropertyName("max_resin")]
|
||||
public int MaxResin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 树脂恢复时间 <see cref="string"/>类型的秒数
|
||||
/// </summary>
|
||||
[JsonPropertyName("resin_recovery_time")]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
public int ResinRecoveryTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的树脂恢复时间
|
||||
/// </summary>
|
||||
public string ResinRecoveryTargetTime
|
||||
{
|
||||
get
|
||||
{
|
||||
DateTime tt = DateTime.Now.AddSeconds(ResinRecoveryTime);
|
||||
int totalDays = (tt - DateTime.Today).Days;
|
||||
string day = totalDays switch
|
||||
{
|
||||
0 => "今天",
|
||||
1 => "明天",
|
||||
2 => "后天",
|
||||
_ => $"{totalDays}天",
|
||||
};
|
||||
return $"{day} {tt:HH:mm}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 委托完成数
|
||||
/// </summary>
|
||||
[JsonPropertyName("finished_task_num")]
|
||||
public int FinishedTaskNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 委托总数
|
||||
/// </summary>
|
||||
[JsonPropertyName("total_task_num")]
|
||||
public int TotalTaskNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 4次委托额外奖励是否领取
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_extra_task_reward_received")]
|
||||
public bool IsExtraTaskRewardReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每日委托奖励字符串
|
||||
/// </summary>
|
||||
public string ExtraTaskRewardDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
return IsExtraTaskRewardReceived
|
||||
? "已领取「每日委托」奖励"
|
||||
: FinishedTaskNum == TotalTaskNum
|
||||
? "「每日委托」奖励待领取"
|
||||
: "今日完成委托次数不足";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 剩余周本折扣次数
|
||||
/// </summary>
|
||||
[JsonPropertyName("remain_resin_discount_num")]
|
||||
public int RemainResinDiscountNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 周本树脂减免使用次数
|
||||
/// </summary>
|
||||
public int ResinDiscountUsedNum
|
||||
{
|
||||
get => ResinDiscountNumLimit - RemainResinDiscountNum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 周本折扣总次数
|
||||
/// </summary>
|
||||
[JsonPropertyName("resin_discount_num_limit")]
|
||||
public int ResinDiscountNumLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前派遣数
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_expedition_num")]
|
||||
public int CurrentExpeditionNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大派遣数
|
||||
/// </summary>
|
||||
[JsonPropertyName("max_expedition_num")]
|
||||
public int MaxExpeditionNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 派遣
|
||||
/// </summary>
|
||||
[JsonPropertyName("expeditions")]
|
||||
public List<Expedition> Expeditions { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 当前洞天宝钱
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_home_coin")]
|
||||
public int CurrentHomeCoin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大洞天宝钱
|
||||
/// </summary>
|
||||
[JsonPropertyName("max_home_coin")]
|
||||
public int MaxHomeCoin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 洞天宝钱恢复时间 <see cref="string"/>类型的秒数
|
||||
/// </summary>
|
||||
[JsonPropertyName("home_coin_recovery_time")]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
public int HomeCoinRecoveryTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的洞天宝钱恢复时间
|
||||
/// </summary>
|
||||
public string HomeCoinRecoveryTargetTimeFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
DateTime reach = DateTime.Now.AddSeconds(HomeCoinRecoveryTime);
|
||||
int totalDays = (reach - DateTime.Today).Days;
|
||||
string day = totalDays switch
|
||||
{
|
||||
0 => "今天",
|
||||
1 => "明天",
|
||||
2 => "后天",
|
||||
_ => $"{totalDays}天",
|
||||
};
|
||||
return $"{day} {reach:HH:mm}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日历链接
|
||||
/// </summary>
|
||||
[JsonPropertyName("calendar_url")]
|
||||
public string CalendarUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 参量质变仪
|
||||
/// </summary>
|
||||
[JsonPropertyName("transformer")]
|
||||
public Transformer Transformer { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
|
||||
/// <summary>
|
||||
/// 探索派遣
|
||||
/// </summary>
|
||||
public class Expedition
|
||||
{
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatar_side_icon")]
|
||||
public Uri AvatarSideIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 状态 Ongoing:派遣中 Finished:已完成
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public ExpeditionStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 剩余时间
|
||||
/// </summary>
|
||||
[JsonPropertyName("remained_time")]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
public int RemainedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的剩余时间
|
||||
/// </summary>
|
||||
public string RemainedTimeFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Status == ExpeditionStatus.Finished)
|
||||
{
|
||||
return "已完成";
|
||||
}
|
||||
|
||||
TimeSpan ts = new(0, 0, RemainedTime);
|
||||
return ts.Hours > 0 ? $"{ts.Hours}时" : $"{ts.Minutes}分";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
|
||||
/// <summary>
|
||||
/// 探索派遣状态
|
||||
/// </summary>
|
||||
public enum ExpeditionStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 进行中
|
||||
/// </summary>
|
||||
Ongoing,
|
||||
|
||||
/// <summary>
|
||||
/// 完成
|
||||
/// </summary>
|
||||
Finished,
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Extension;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
|
||||
/// <summary>
|
||||
/// 参量质变仪恢复时间包装
|
||||
/// 已准备完成 $后可再次使用
|
||||
/// 冷却中 可使用
|
||||
/// </summary>
|
||||
public class RecoveryTime
|
||||
{
|
||||
/// <summary>
|
||||
/// 日
|
||||
/// </summary>
|
||||
[JsonPropertyName("Day")]
|
||||
public int Day { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时
|
||||
/// </summary>
|
||||
[JsonPropertyName("Hour")]
|
||||
public int Hour { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分
|
||||
/// </summary>
|
||||
[JsonPropertyName("Minute")]
|
||||
public int Minute { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 秒
|
||||
/// </summary>
|
||||
[JsonPropertyName("Second")]
|
||||
public int Second { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已经到达
|
||||
/// </summary>
|
||||
[JsonPropertyName("reached")]
|
||||
public bool Reached { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取格式化的剩余时间
|
||||
/// </summary>
|
||||
public string TimeFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Reached)
|
||||
{
|
||||
return "已准备完成";
|
||||
}
|
||||
else
|
||||
{
|
||||
return new StringBuilder()
|
||||
.AppendIf(Day > 0, $"{Day}天")
|
||||
.AppendIf(Hour > 0, $"{Hour}时")
|
||||
.AppendIf(Minute > 0, $"{Minute}分")
|
||||
.AppendIf(Second > 0, $"{Second}秒")
|
||||
.Append(" 后可再次使用")
|
||||
.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取格式化的状态
|
||||
/// </summary>
|
||||
public string ReachedFormatted
|
||||
{
|
||||
get => Reached ? "可使用" : "冷却中";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
|
||||
/// <summary>
|
||||
/// 参量质变仪
|
||||
/// </summary>
|
||||
public class Transformer
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否拥有该道具
|
||||
/// </summary>
|
||||
[JsonPropertyName("obtained")]
|
||||
public bool Obtained { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 恢复时间包装
|
||||
/// </summary>
|
||||
[JsonPropertyName("recovery_time")]
|
||||
public RecoveryTime? RecoveryTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Wiki链接
|
||||
/// </summary>
|
||||
[JsonPropertyName("wiki")]
|
||||
public Uri Wiki { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 是否提醒
|
||||
/// </summary>
|
||||
[JsonPropertyName("noticed")]
|
||||
public bool Noticed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上个任务的Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("latest_job_id")]
|
||||
public string LastJobId { get; set; } = default!;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
using Snap.Hutao.Web.Response;
|
||||
@@ -34,6 +34,42 @@ internal class GameRecordClient
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取实时便笺
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="uid">查询uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>实时便笺</returns>
|
||||
public async Task<DailyNote.DailyNote?> GetDialyNoteAsync(User user, PlayerUid uid, CancellationToken token = default)
|
||||
{
|
||||
Response<DailyNote.DailyNote>? resp = await httpClient
|
||||
.SetUser(user)
|
||||
.UsingDynamicSecret(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region))
|
||||
.GetFromJsonAsync<Response<DailyNote.DailyNote>>(token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取实时便笺
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="uid">查询uid</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>实时便笺</returns>
|
||||
public async Task<DailyNote.DailyNote?> GetDialyNoteAsync(Model.Entity.User user, PlayerUid uid, CancellationToken token = default)
|
||||
{
|
||||
Response<DailyNote.DailyNote>? resp = await httpClient
|
||||
.SetUser(user)
|
||||
.UsingDynamicSecret(options, ApiEndpoints.GameRecordDailyNote(uid.Value, uid.Region))
|
||||
.GetFromJsonAsync<Response<DailyNote.DailyNote>>(token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return resp?.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家基础信息
|
||||
/// </summary>
|
||||
@@ -68,7 +104,7 @@ internal class GameRecordClient
|
||||
/// 获取玩家深渊信息
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="schedule">1:当期,2:上期</param>
|
||||
/// <param name="schedule">期</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>深渊信息</returns>
|
||||
public Task<SpiralAbyss.SpiralAbyss?> GetSpiralAbyssAsync(User user, SpiralAbyssSchedule schedule, CancellationToken token = default)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Binding.User;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;
|
||||
@@ -172,7 +173,7 @@ internal class HomaClient
|
||||
/// <param name="user">用户</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>玩家记录</returns>
|
||||
public async Task<SimpleRecord> GetPlayerRecordAsync(Snap.Hutao.Model.Binding.User user, CancellationToken token = default)
|
||||
public async Task<SimpleRecord> GetPlayerRecordAsync(User user, CancellationToken token = default)
|
||||
{
|
||||
PlayerInfo? playerInfo = await gameRecordClient
|
||||
.GetPlayerInfoAsync(user, token)
|
||||
|
||||
@@ -41,7 +41,7 @@ public enum KnownReturnCode : int
|
||||
/// <summary>
|
||||
/// 访问过于频繁
|
||||
/// </summary>
|
||||
VIsitTooFrequently = -110,
|
||||
VisitTooFrequently = -110,
|
||||
|
||||
/// <summary>
|
||||
/// 应用Id错误
|
||||
|
||||
@@ -89,14 +89,4 @@ public class Response<TData> : Response
|
||||
{
|
||||
return ReturnCode == 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int j = ReturnCode.GetHashCode();
|
||||
int k = Message == null ? 0 : Message.GetHashCode();
|
||||
int i = Data == null ? 0 : Data.GetHashCode();
|
||||
|
||||
return (((j * 31) + k) * 31) + i;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user