mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
prevent user service capture scoped app db context
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -56,6 +56,11 @@ public class AppDbContext : DbContext
|
||||
/// </summary>
|
||||
public DbSet<AvatarInfo> AvatarInfos { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏内账号
|
||||
/// </summary>
|
||||
public DbSet<GameAccount> GameAccounts { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个临时的应用程序数据库上下文
|
||||
/// </summary>
|
||||
|
||||
@@ -9,8 +9,9 @@ namespace Snap.Hutao.Core.Threading;
|
||||
internal static class ThreadHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步切换到主线程
|
||||
/// 使用此静态方法以 异步切换到 主线程
|
||||
/// </summary>
|
||||
/// <remarks>使用 <see cref="Task.Yield"/> 异步切换到 后台线程</remarks>
|
||||
/// <returns>等待体</returns>
|
||||
public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
|
||||
{
|
||||
|
||||
@@ -13,7 +13,12 @@ public class HistoryWish : WishBase
|
||||
/// <summary>
|
||||
/// 版本
|
||||
/// </summary>
|
||||
public string Version { get; set; }
|
||||
public string Version { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 卡池图片
|
||||
/// </summary>
|
||||
public Uri BannerImage { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 五星Up
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.LaunchGame;
|
||||
|
||||
/// <summary>
|
||||
/// 服务器方案
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 启动方案
|
||||
/// </summary>
|
||||
public class LaunchScheme
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的启动方案
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <param name="channel">通道</param>
|
||||
/// <param name="cps">通道描述字符串</param>
|
||||
/// <param name="subChannel">子通道</param>
|
||||
public LaunchScheme(string name, string channel, string subChannel)
|
||||
{
|
||||
Name = name;
|
||||
Channel = channel;
|
||||
SubChannel = subChannel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道
|
||||
/// </summary>
|
||||
public string Channel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 子通道
|
||||
/// </summary>
|
||||
public string SubChannel { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.LaunchGame;
|
||||
|
||||
/// <summary>
|
||||
/// 启动类型
|
||||
/// </summary>
|
||||
public enum SchemeType
|
||||
{
|
||||
/// <summary>
|
||||
/// 国际服
|
||||
/// </summary>
|
||||
Mihoyo,
|
||||
|
||||
/// <summary>
|
||||
/// 国服官服
|
||||
/// </summary>
|
||||
Officical,
|
||||
|
||||
/// <summary>
|
||||
/// 渠道服
|
||||
/// </summary>
|
||||
Bilibili,
|
||||
}
|
||||
47
src/Snap.Hutao/Snap.Hutao/Model/Entity/GameAccount.cs
Normal file
47
src/Snap.Hutao/Snap.Hutao/Model/Entity/GameAccount.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Binding.LaunchGame;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏内账号
|
||||
/// </summary>
|
||||
[Table("game_accounts")]
|
||||
public class GameAccount : ISelectable
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
/// </summary>
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid InnerId { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 对应的Uid
|
||||
/// </summary>
|
||||
public string? AttachUid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务器类型
|
||||
/// </summary>
|
||||
public SchemeType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// MIHOYOSDK_ADL_PROD_CN_h3123967166
|
||||
/// </summary>
|
||||
public string MihoyoSDK { get; set; } = default!;
|
||||
}
|
||||
@@ -40,4 +40,4 @@ public class User : ISelectable
|
||||
{
|
||||
return new() { Cookie = cookie };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,11 @@ public class GachaEvent
|
||||
/// </summary>
|
||||
public string Version { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 卡池图
|
||||
/// </summary>
|
||||
public Uri Banner { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间
|
||||
/// </summary>
|
||||
|
||||
35
src/Snap.Hutao/Snap.Hutao/Model/NamedValue.cs
Normal file
35
src/Snap.Hutao/Snap.Hutao/Model/NamedValue.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Snap.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 封装带有名称描述的值
|
||||
/// 在绑定枚举变量时非常有用
|
||||
/// </summary>
|
||||
/// <typeparam name="T">包含值的类型</typeparam>
|
||||
public class NamedValue<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的命名的值
|
||||
/// </summary>
|
||||
/// <param name="name">命名</param>
|
||||
/// <param name="value">值</param>
|
||||
public NamedValue(string name, T value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public T Value { get; }
|
||||
}
|
||||
@@ -46,4 +46,4 @@ public class Selectable<T> : ObservableObject
|
||||
/// 存放的对象
|
||||
/// </summary>
|
||||
public T Value { get => value; set => SetProperty(ref this.value, value); }
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,7 @@ internal class HistoryWishBuilder
|
||||
|
||||
// fill
|
||||
Version = gachaEvent.Version,
|
||||
BannerImage = gachaEvent.Banner,
|
||||
OrangeUpList = orangeUpCounter.ToStatisticsList(),
|
||||
PurpleUpList = purpleUpCounter.ToStatisticsList(),
|
||||
OrangeList = orangeCounter.ToStatisticsList(),
|
||||
|
||||
@@ -6,10 +6,12 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.IO.Ini;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Game.Locator;
|
||||
using Snap.Hutao.Service.Game.Unlocker;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
@@ -27,6 +29,8 @@ internal class GameService : IGameService
|
||||
private readonly IMemoryCache memoryCache;
|
||||
private readonly SemaphoreSlim gameSemaphore = new(1);
|
||||
|
||||
private ObservableCollection<GameAccount>? gameAccounts;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的游戏服务
|
||||
/// </summary>
|
||||
@@ -128,52 +132,78 @@ internal class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MultiChannel GetMultiChannel()
|
||||
{
|
||||
string gamePath = GetGamePathSkipLocator();
|
||||
string configPath = Path.Combine(gamePath, "config.ini");
|
||||
|
||||
using (FileStream stream = File.OpenRead(configPath))
|
||||
{
|
||||
List<IniElement> elements = IniSerializer.Deserialize(stream).ToList();
|
||||
string? channel = elements.OfType<IniParameter>().FirstOrDefault(p => p.Key == "channel")?.Value;
|
||||
string? subChannel = elements.OfType<IniParameter>().FirstOrDefault(p => p.Key == "sub_channel")?.Value;
|
||||
|
||||
return new(channel, subChannel);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<GameAccount>> GetGameAccountCollectionAsync()
|
||||
{
|
||||
if (gameAccounts == null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return gameAccounts;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask LaunchAsync(LaunchConfiguration configuration)
|
||||
{
|
||||
(bool isOk, string gamePath) = await GetGamePathAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
if (gameSemaphore.CurrentCount == 0)
|
||||
{
|
||||
if (gameSemaphore.CurrentCount == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
string gamePath = GetGamePathSkipLocator();
|
||||
|
||||
string commandLine = new CommandLineBuilder()
|
||||
.AppendIf("-popupwindow", configuration.IsBorderless)
|
||||
.Append("-screen-fullscreen", configuration.IsFullScreen ? 1 : 0)
|
||||
.Append("-screen-width", configuration.ScreenWidth)
|
||||
.Append("-screen-height", configuration.ScreenHeight)
|
||||
.Append("-monitor", configuration.Monitor)
|
||||
.Build();
|
||||
|
||||
Process game = new()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
return;
|
||||
Arguments = commandLine,
|
||||
FileName = gamePath,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas",
|
||||
WorkingDirectory = Path.GetDirectoryName(gamePath),
|
||||
},
|
||||
};
|
||||
|
||||
using (await gameSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (configuration.UnlockFPS)
|
||||
{
|
||||
IGameFpsUnlocker unlocker = new GameFpsUnlocker(game, configuration.TargetFPS);
|
||||
|
||||
TimeSpan findModuleDelay = TimeSpan.FromMilliseconds(100);
|
||||
TimeSpan findModuleLimit = TimeSpan.FromMilliseconds(10000);
|
||||
TimeSpan adjustFpsDelay = TimeSpan.FromMilliseconds(2000);
|
||||
await unlocker.UnlockAsync(findModuleDelay, findModuleLimit, adjustFpsDelay).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
string commandLine = new CommandLineBuilder()
|
||||
.Append("-window-mode", configuration.WindowMode)
|
||||
.Append("-screen-fullscreen", configuration.IsFullScreen ? 1 : 0)
|
||||
.Append("-screen-width", configuration.ScreenWidth)
|
||||
.Append("-screen-height", configuration.ScreenHeight)
|
||||
.Append("-monitor", configuration.Monitor)
|
||||
.Build();
|
||||
|
||||
Process game = new()
|
||||
else
|
||||
{
|
||||
StartInfo = new()
|
||||
if (game.Start())
|
||||
{
|
||||
Arguments = commandLine,
|
||||
FileName = gamePath,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas",
|
||||
WorkingDirectory = Path.GetDirectoryName(gamePath),
|
||||
},
|
||||
};
|
||||
|
||||
using (await gameSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (configuration.UnlockFPS)
|
||||
{
|
||||
IGameFpsUnlocker unlocker = new GameFpsUnlocker(game, configuration.TargetFPS);
|
||||
|
||||
await unlocker.UnlockAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(10000), TimeSpan.FromMilliseconds(2000)).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (game.Start())
|
||||
{
|
||||
await game.WaitForExitAsync().ConfigureAwait(false);
|
||||
}
|
||||
await game.WaitForExitAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,12 @@ internal interface IGameService
|
||||
/// <returns>游戏路径,当路径无效时会设置并返回 <see cref="string.Empty"/></returns>
|
||||
string GetGamePathSkipLocator();
|
||||
|
||||
/// <summary>
|
||||
/// 获取多通道值
|
||||
/// </summary>
|
||||
/// <returns>多通道值</returns>
|
||||
MultiChannel GetMultiChannel();
|
||||
|
||||
/// <summary>
|
||||
/// 异步启动
|
||||
/// </summary>
|
||||
|
||||
@@ -14,9 +14,9 @@ internal struct LaunchConfiguration
|
||||
public bool IsFullScreen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override fullscreen windowed mode. Accepted values are exclusive or borderless.
|
||||
/// 是否为无边框窗口
|
||||
/// </summary>
|
||||
public string WindowMode { get; private set; }
|
||||
public bool IsBorderless { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用解锁帧率
|
||||
|
||||
31
src/Snap.Hutao/Snap.Hutao/Service/Game/MultiChannel.cs
Normal file
31
src/Snap.Hutao/Snap.Hutao/Service/Game/MultiChannel.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Game;
|
||||
|
||||
/// <summary>
|
||||
/// 多通道
|
||||
/// </summary>
|
||||
public struct MultiChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// 通道
|
||||
/// </summary>
|
||||
public string Channel;
|
||||
|
||||
/// <summary>
|
||||
/// 子通道
|
||||
/// </summary>
|
||||
public string SubChannel;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的多通道
|
||||
/// </summary>
|
||||
/// <param name="channel">通道</param>
|
||||
/// <param name="subChannel">子通道</param>
|
||||
public MultiChannel(string? channel, string? subChannel)
|
||||
{
|
||||
Channel = channel ?? string.Empty;
|
||||
SubChannel = subChannel ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -72,4 +72,9 @@ public interface INavigationService
|
||||
/// <param name="pageType">同步的页面类型</param>
|
||||
/// <returns>是否同步成功</returns>
|
||||
bool SyncSelectedNavigationViewItemWith(Type pageType);
|
||||
|
||||
/// <summary>
|
||||
/// 尽可能尝试返回
|
||||
/// </summary>
|
||||
void GoBack();
|
||||
}
|
||||
|
||||
@@ -178,6 +178,18 @@ internal class NavigationService : INavigationService
|
||||
NavigationView.IsPaneOpen = LocalSetting.Get(SettingKeys.IsNavPaneOpen, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void GoBack()
|
||||
{
|
||||
bool canGoBack = Frame?.CanGoBack ?? false;
|
||||
|
||||
if (canGoBack)
|
||||
{
|
||||
Frame!.GoBack();
|
||||
SyncSelectedNavigationViewItemWith(Frame.Content.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 遍历所有子菜单项
|
||||
/// </summary>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
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.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
@@ -20,10 +22,7 @@ namespace Snap.Hutao.Service.User;
|
||||
[Injection(InjectAs.Singleton, typeof(IUserService))]
|
||||
internal class UserService : IUserService
|
||||
{
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly UserClient userClient;
|
||||
private readonly BindingClient bindingClient;
|
||||
private readonly AuthClient authClient;
|
||||
private readonly IServiceScopeFactory scopeFactory;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
private BindingUser? currentUser;
|
||||
@@ -32,22 +31,11 @@ internal class UserService : IUserService
|
||||
/// <summary>
|
||||
/// 构造一个新的用户服务
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">应用程序数据库上下文</param>
|
||||
/// <param name="userClient">用户客户端</param>
|
||||
/// <param name="bindingClient">角色客户端</param>
|
||||
/// <param name="authClient">验证客户端</param>
|
||||
/// <param name="scopeFactory">范围工厂</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public UserService(
|
||||
AppDbContext appDbContext,
|
||||
UserClient userClient,
|
||||
BindingClient bindingClient,
|
||||
AuthClient authClient,
|
||||
IMessenger messenger)
|
||||
public UserService(IServiceScopeFactory scopeFactory, IMessenger messenger)
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
this.userClient = userClient;
|
||||
this.bindingClient = bindingClient;
|
||||
this.authClient = authClient;
|
||||
this.scopeFactory = scopeFactory;
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
@@ -62,44 +50,54 @@ internal class UserService : IUserService
|
||||
return;
|
||||
}
|
||||
|
||||
// only update when not processing a deletion
|
||||
if (value != null)
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// only update when not processing a deletion
|
||||
if (value != null)
|
||||
{
|
||||
if (currentUser != null)
|
||||
{
|
||||
currentUser.IsSelected = false;
|
||||
appDbContext.Users.Update(currentUser.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
Message.UserChangedMessage message = new() { OldValue = currentUser, NewValue = value };
|
||||
|
||||
// 当删除到无用户时也能正常反应状态
|
||||
currentUser = value;
|
||||
|
||||
if (currentUser != null)
|
||||
{
|
||||
currentUser.IsSelected = false;
|
||||
currentUser.IsSelected = true;
|
||||
appDbContext.Users.Update(currentUser.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
messenger.Send(message);
|
||||
}
|
||||
|
||||
Message.UserChangedMessage message = new() { OldValue = currentUser, NewValue = value };
|
||||
|
||||
// 当删除到无用户时也能正常反应状态
|
||||
currentUser = value;
|
||||
|
||||
if (currentUser != null)
|
||||
{
|
||||
currentUser.IsSelected = true;
|
||||
appDbContext.Users.Update(currentUser.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
messenger.Send(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RemoveUserAsync(BindingUser user)
|
||||
public async Task RemoveUserAsync(BindingUser user)
|
||||
{
|
||||
await Task.Yield();
|
||||
Must.NotNull(userCollection!);
|
||||
|
||||
// Sync cache
|
||||
userCollection.Remove(user);
|
||||
|
||||
// Sync database
|
||||
appDbContext.Users.Remove(user.Entity);
|
||||
return appDbContext.SaveChangesAsync();
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
appDbContext.Users.RemoveAndSave(user.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -109,21 +107,27 @@ internal class UserService : IUserService
|
||||
{
|
||||
List<BindingUser> users = new();
|
||||
|
||||
foreach (Model.Entity.User entity in appDbContext.Users)
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
BindingUser? initialized = await BindingUser
|
||||
.ResumeAsync(entity, userClient, bindingClient)
|
||||
.ConfigureAwait(false);
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
UserClient userClient = scope.ServiceProvider.GetRequiredService<UserClient>();
|
||||
BindingClient bindingClient = scope.ServiceProvider.GetRequiredService<BindingClient>();
|
||||
|
||||
if (initialized != null)
|
||||
foreach (Model.Entity.User entity in appDbContext.Users)
|
||||
{
|
||||
users.Add(initialized);
|
||||
}
|
||||
else
|
||||
{
|
||||
// User is unable to be initialized, remove it.
|
||||
appDbContext.Users.Remove(entity);
|
||||
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
BindingUser? initialized = await BindingUser
|
||||
.ResumeAsync(entity, userClient, bindingClient)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (initialized != null)
|
||||
{
|
||||
users.Add(initialized);
|
||||
}
|
||||
else
|
||||
{
|
||||
// User is unable to be initialized, remove it.
|
||||
appDbContext.Users.RemoveAndSave(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,24 +154,27 @@ internal class UserService : IUserService
|
||||
// 检查 uid 对应用户是否存在
|
||||
if (UserHelper.TryGetUserByUid(userCollection, uid, out BindingUser? userWithSameUid))
|
||||
{
|
||||
// 检查 stoken 是否存在
|
||||
if (cookie.ContainsSToken())
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
// insert stoken
|
||||
userWithSameUid.UpdateSToken(uid, cookie);
|
||||
appDbContext.Users.Update(userWithSameUid.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
return new(UserOptionResult.Upgraded, uid);
|
||||
}
|
||||
// 检查 stoken 是否存在
|
||||
if (cookie.ContainsSToken())
|
||||
{
|
||||
// insert stoken
|
||||
userWithSameUid.UpdateSToken(uid, cookie);
|
||||
appDbContext.Users.UpdateAndSave(userWithSameUid.Entity);
|
||||
|
||||
if (cookie.ContainsLTokenAndCookieToken())
|
||||
{
|
||||
userWithSameUid.Cookie = cookie;
|
||||
appDbContext.Users.Update(userWithSameUid.Entity);
|
||||
appDbContext.SaveChanges();
|
||||
return new(UserOptionResult.Upgraded, uid);
|
||||
}
|
||||
|
||||
return new(UserOptionResult.Updated, uid);
|
||||
if (cookie.ContainsLTokenAndCookieToken())
|
||||
{
|
||||
userWithSameUid.Cookie = cookie;
|
||||
appDbContext.Users.UpdateAndSave(userWithSameUid.Entity);
|
||||
|
||||
return new(UserOptionResult.Updated, uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cookie.ContainsLTokenAndCookieToken())
|
||||
@@ -184,7 +191,8 @@ internal class UserService : IUserService
|
||||
if (cookie.TryGetLoginTicket(out string? loginTicket))
|
||||
{
|
||||
// get multitoken
|
||||
Dictionary<string, string> multiToken = await authClient
|
||||
Dictionary<string, string> multiToken = await Ioc.Default
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicket, uid, default)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -198,22 +206,27 @@ internal class UserService : IUserService
|
||||
|
||||
private async Task<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(ObservableCollection<BindingUser> users, Cookie cookie)
|
||||
{
|
||||
BindingUser? newUser = await BindingUser.CreateAsync(cookie, userClient, bindingClient).ConfigureAwait(false);
|
||||
if (newUser != null)
|
||||
using (IServiceScope scope = scopeFactory.CreateScope())
|
||||
{
|
||||
// Sync cache
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
users.Add(newUser);
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
UserClient userClient = scope.ServiceProvider.GetRequiredService<UserClient>();
|
||||
BindingClient bindingClient = scope.ServiceProvider.GetRequiredService<BindingClient>();
|
||||
|
||||
// Sync database
|
||||
appDbContext.Users.Add(newUser.Entity);
|
||||
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
BindingUser? newUser = await BindingUser.CreateAsync(cookie, userClient, bindingClient).ConfigureAwait(false);
|
||||
if (newUser != null)
|
||||
{
|
||||
// Sync cache
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
users.Add(newUser);
|
||||
|
||||
return new(UserOptionResult.Added, newUser.UserInfo!.Uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(UserOptionResult.Invalid, null!);
|
||||
// Sync database
|
||||
appDbContext.Users.AddAndSave(newUser.Entity);
|
||||
return new(UserOptionResult.Added, newUser.UserInfo!.Uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(UserOptionResult.Invalid, null!);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,8 @@
|
||||
<None Remove="View\Page\GachaLogPage.xaml" />
|
||||
<None Remove="View\Page\HutaoDatabasePage.xaml" />
|
||||
<None Remove="View\Page\LaunchGamePage.xaml" />
|
||||
<None Remove="View\Page\LoginMihoyoBBSPage.xaml" />
|
||||
<None Remove="View\Page\LoginMihoyoUserPage.xaml" />
|
||||
<None Remove="View\Page\SettingPage.xaml" />
|
||||
<None Remove="View\Page\WikiAvatarPage.xaml" />
|
||||
<None Remove="View\TitleView.xaml" />
|
||||
@@ -148,6 +150,16 @@
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Page\LoginMihoyoBBSPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Page\LoginMihoyoUserPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="LaunchGameWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -138,7 +138,8 @@
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<TextBlock
|
||||
Margin="0,4,12,4"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0,4,12,2"
|
||||
FontFamily="Consolas"
|
||||
Text="{Binding TotalCount}"
|
||||
Visibility="{Binding ElementName=DetailExpander,Path=IsExpanded,Converter={StaticResource BoolToVisibilityRevertConverter}}"
|
||||
@@ -318,7 +319,6 @@
|
||||
</ItemsControl>
|
||||
</cwucont:Case>
|
||||
</cwucont:SwitchPresenter>
|
||||
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -241,8 +241,20 @@
|
||||
</ListView>
|
||||
</SplitView.Pane>
|
||||
<SplitView.Content>
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="16,0,16,0">
|
||||
<Border
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0,16,0,0"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefault}"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}">
|
||||
<shci:CachedImage
|
||||
MaxHeight="320"
|
||||
Source="{Binding SelectedHistoryWish.BannerImage}"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="五星" Style="{StaticResource BaseTextBlockStyle}" Margin="0,16,0,8"/>
|
||||
<GridView ItemsSource="{Binding SelectedHistoryWish.OrangeList}">
|
||||
<GridView.ItemTemplate>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer Grid.Column="0">
|
||||
<ScrollViewer Grid.Column="0" CanContentRenderOutsideBounds="True">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MaxWidth="800"/>
|
||||
@@ -37,6 +37,36 @@
|
||||
<ComboBox Width="120"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
<sc:SettingExpander>
|
||||
<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"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,8,0"
|
||||
Width="80"
|
||||
MinWidth="88"
|
||||
Content="检测"/>
|
||||
</Grid>
|
||||
|
||||
</sc:SettingExpander.Header>
|
||||
</sc:SettingExpander>
|
||||
</sc:SettingsGroup>
|
||||
<sc:SettingsGroup Header="外观">
|
||||
<sc:Setting
|
||||
@@ -111,19 +141,10 @@
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ComboBox
|
||||
Header="原神账号"
|
||||
Grid.Column="0"
|
||||
Margin="32,24,0,24"
|
||||
Width="160"/>
|
||||
Background="{StaticResource SystemControlAcrylicElementMediumHighBrush}">
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Grid.Column="3"
|
||||
Margin="24"
|
||||
Width="138"
|
||||
Content="启动游戏"/>
|
||||
|
||||
31
src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoBBSPage.xaml
Normal file
31
src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoBBSPage.xaml
Normal file
@@ -0,0 +1,31 @@
|
||||
<Page
|
||||
x:Class="Snap.Hutao.View.Page.LoginMihoyoBBSPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid Loaded="OnRootLoaded">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="在下方登录米游社"
|
||||
Grid.Row="0"/>
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
Margin="16"
|
||||
Content="我已登录"
|
||||
Click="CookieButtonClick"/>
|
||||
<WebView2
|
||||
Grid.Row="1"
|
||||
Margin="0,0,0,0"
|
||||
|
||||
x:Name="WebView"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
|
||||
/// <summary>
|
||||
/// 登录米游社页面
|
||||
/// </summary>
|
||||
public sealed partial class LoginMihoyoBBSPage : Microsoft.UI.Xaml.Controls.Page
|
||||
{
|
||||
private const string CookieSite = "https://bbs.mihoyo.com";
|
||||
private const string Website = "https://bbs.mihoyo.com/ys/";
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的登录米游社页面
|
||||
/// </summary>
|
||||
public LoginMihoyoBBSPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD100")]
|
||||
private async void OnRootLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await WebView.EnsureCoreWebView2Async();
|
||||
|
||||
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
|
||||
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync(CookieSite);
|
||||
foreach (CoreWebView2Cookie item in cookies)
|
||||
{
|
||||
manager.DeleteCookie(item);
|
||||
}
|
||||
|
||||
WebView.CoreWebView2.Navigate(Website);
|
||||
}
|
||||
|
||||
private async Task HandleCurrentCookieAsync()
|
||||
{
|
||||
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
|
||||
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync(CookieSite);
|
||||
|
||||
Cookie cookie = Cookie.FromCoreWebView2Cookies(cookies);
|
||||
IUserService userService = Ioc.Default.GetRequiredService<IUserService>();
|
||||
(UserOptionResult result, string nickname) = await userService.ProcessInputCookieAsync(cookie).ConfigureAwait(false);
|
||||
|
||||
Ioc.Default.GetRequiredService<INavigationService>().GoBack();
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case UserOptionResult.Added:
|
||||
infoBarService.Success($"用户 [{nickname}] 添加成功");
|
||||
break;
|
||||
case UserOptionResult.Incomplete:
|
||||
infoBarService.Information($"此 Cookie 不完整,操作失败");
|
||||
break;
|
||||
case UserOptionResult.Invalid:
|
||||
infoBarService.Information($"此 Cookie 无法,操作失败");
|
||||
break;
|
||||
case UserOptionResult.Updated:
|
||||
infoBarService.Success($"用户 [{nickname}] 更新成功");
|
||||
break;
|
||||
case UserOptionResult.Upgraded:
|
||||
infoBarService.Information($"用户 [{nickname}] 升级成功");
|
||||
break;
|
||||
default:
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
}
|
||||
|
||||
private void CookieButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HandleCurrentCookieAsync().SafeForget();
|
||||
}
|
||||
}
|
||||
30
src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml
Normal file
30
src/Snap.Hutao/Snap.Hutao/View/Page/LoginMihoyoUserPage.xaml
Normal file
@@ -0,0 +1,30 @@
|
||||
<Page
|
||||
x:Class="Snap.Hutao.View.Page.LoginMihoyoUserPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid Loaded="OnRootLoaded">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="在下方米哈游通行证"
|
||||
Grid.Row="0"/>
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
Margin="16"
|
||||
Content="我已登录"
|
||||
Click="CookieButtonClick"/>
|
||||
<WebView2
|
||||
Grid.Row="2"
|
||||
Margin="0,0,0,0"
|
||||
x:Name="WebView"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
|
||||
/// <summary>
|
||||
/// 登录米哈游通行证页面
|
||||
/// </summary>
|
||||
public sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.Page
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的登录米哈游通行证页面
|
||||
/// </summary>
|
||||
public LoginMihoyoUserPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD100")]
|
||||
private async void OnRootLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await WebView.EnsureCoreWebView2Async();
|
||||
WebView.CoreWebView2.SourceChanged += OnCoreWebView2SourceChanged;
|
||||
|
||||
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
|
||||
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
|
||||
foreach (CoreWebView2Cookie item in cookies)
|
||||
{
|
||||
manager.DeleteCookie(item);
|
||||
}
|
||||
|
||||
WebView.CoreWebView2.Navigate("https://user.mihoyo.com/#/login/password");
|
||||
}
|
||||
|
||||
[SuppressMessage("", "VSTHRD100")]
|
||||
private async void OnCoreWebView2SourceChanged(CoreWebView2 sender, CoreWebView2SourceChangedEventArgs args)
|
||||
{
|
||||
if (sender != null)
|
||||
{
|
||||
if (sender.Source.ToString() == "https://user.mihoyo.com/#/account/home")
|
||||
{
|
||||
await HandleCurrentCookieAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCurrentCookieAsync()
|
||||
{
|
||||
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
|
||||
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
|
||||
WebView.CoreWebView2.SourceChanged -= OnCoreWebView2SourceChanged;
|
||||
|
||||
Cookie cookie = Cookie.FromCoreWebView2Cookies(cookies);
|
||||
IUserService userService = Ioc.Default.GetRequiredService<IUserService>();
|
||||
(UserOptionResult result, string nickname) = await userService.ProcessInputCookieAsync(cookie).ConfigureAwait(false);
|
||||
|
||||
Ioc.Default.GetRequiredService<INavigationService>().GoBack();
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
|
||||
if (result == UserOptionResult.Upgraded)
|
||||
{
|
||||
infoBarService.Information($"用户 [{nickname}] 的 Cookie 升级成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning("请先添加对应用户的米游社Cookie");
|
||||
}
|
||||
}
|
||||
|
||||
private void CookieButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HandleCurrentCookieAsync().SafeForget();
|
||||
}
|
||||
}
|
||||
@@ -205,12 +205,16 @@
|
||||
<TextBlock
|
||||
Margin="10,6,0,6"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="Cookie操作"/>
|
||||
Text="Cookie 操作"/>
|
||||
<CommandBar DefaultLabelPosition="Right">
|
||||
<AppBarButton
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="登录米哈游通行证"
|
||||
Command="{Binding UpgradeToStokenCommand}"/>
|
||||
<AppBarButton Label="网页登录" Icon="{shcm:FontIcon Glyph=}">
|
||||
<AppBarButton.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem Icon="{shcm:FontIcon Glyph=}" Text="登录米游社原神社区" Command="{Binding LoginMihoyoBBSCommand}"/>
|
||||
<MenuFlyoutItem Icon="{shcm:FontIcon Glyph=}" Text="登录米哈游通行证" Command="{Binding LoginMihoyoUserCommand}"/>
|
||||
</MenuFlyout>
|
||||
</AppBarButton.Flyout>
|
||||
</AppBarButton>
|
||||
<AppBarButton
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="手动输入"
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Binding.LaunchGame;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
@@ -12,6 +17,148 @@ namespace Snap.Hutao.ViewModel;
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
private readonly IGameService gameService;
|
||||
|
||||
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: "0"),
|
||||
};
|
||||
|
||||
private LaunchScheme? selectedScheme;
|
||||
private ObservableCollection<GameAccount>? gameAccounts;
|
||||
private GameAccount? selectedGameAccount;
|
||||
private bool isFullScreen;
|
||||
private bool isBorderless;
|
||||
private int screenWidth;
|
||||
private int screenHeight;
|
||||
private bool unlockFps;
|
||||
private int targetFps;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的启动游戏视图模型
|
||||
/// </summary>
|
||||
/// <param name="gameService">游戏服务</param>
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
public LaunchGameViewModel(IGameService gameService, IAsyncRelayCommandFactory asyncRelayCommandFactory)
|
||||
{
|
||||
this.gameService = gameService;
|
||||
|
||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||
LaunchCommand = asyncRelayCommandFactory.Create(LaunchAsync);
|
||||
DetectGameAccountCommand = asyncRelayCommandFactory.Create(DetectGameAccountAsync);
|
||||
ModifyGameAccountCommand = asyncRelayCommandFactory.Create(ModifyGameAccountAsync);
|
||||
RemoveGameAccountCommand = asyncRelayCommandFactory.Create(RemoveGameAccountAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 已知的服务器方案
|
||||
/// </summary>
|
||||
public List<LaunchScheme> KnownSchemes { get => knownSchemes; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前选择的服务器方案
|
||||
/// </summary>
|
||||
public LaunchScheme? SelectedScheme { get => selectedScheme; set => SetProperty(ref selectedScheme, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 游戏账号集合
|
||||
/// </summary>
|
||||
public ObservableCollection<GameAccount>? GameAccounts { get => gameAccounts; set => SetProperty(ref gameAccounts, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 选中的账号
|
||||
/// </summary>
|
||||
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 全屏
|
||||
/// </summary>
|
||||
public bool IsFullScreen { get => isFullScreen; set => SetProperty(ref isFullScreen, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 无边框
|
||||
/// </summary>
|
||||
public bool IsBorderless { get => isBorderless; set => SetProperty(ref isBorderless, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 宽度
|
||||
/// </summary>
|
||||
public int ScreenWidth { get => screenWidth; set => SetProperty(ref screenWidth, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 高度
|
||||
/// </summary>
|
||||
public int ScreenHeight { get => screenHeight; set => SetProperty(ref screenHeight, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 解锁帧率
|
||||
/// </summary>
|
||||
public bool UnlockFps { get => unlockFps; set => SetProperty(ref unlockFps, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 目标帧率
|
||||
/// </summary>
|
||||
public int TargetFps { get => targetFps; set => SetProperty(ref targetFps, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 打开界面命令
|
||||
/// </summary>
|
||||
public ICommand OpenUICommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏命令
|
||||
/// </summary>
|
||||
public ICommand LaunchCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 检测游戏账号命令
|
||||
/// </summary>
|
||||
public ICommand DetectGameAccountCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 修改游戏账号命令
|
||||
/// </summary>
|
||||
public ICommand ModifyGameAccountCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 删除游戏账号命令
|
||||
/// </summary>
|
||||
public ICommand RemoveGameAccountCommand { get; }
|
||||
|
||||
private async Task OpenUIAsync()
|
||||
{
|
||||
(bool isOk, string gamePath) = await gameService.GetGamePathAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
MultiChannel multi = gameService.GetMultiChannel();
|
||||
SelectedScheme = KnownSchemes.FirstOrDefault(s => s.Channel == multi.Channel && s.SubChannel == multi.SubChannel);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private async Task DetectGameAccountAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private async Task ModifyGameAccountAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private async Task RemoveGameAccountAsync()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@ using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Binding;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.View.Page;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
@@ -40,7 +42,8 @@ internal class UserViewModel : ObservableObject
|
||||
|
||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||
AddUserCommand = asyncRelayCommandFactory.Create(AddUserAsync);
|
||||
UpgradeToStokenCommand = asyncRelayCommandFactory.Create(UpgradeByLoginTicketAsync);
|
||||
LoginMihoyoUserCommand = new RelayCommand(LoginMihoyoUser);
|
||||
LoginMihoyoBBSCommand = new RelayCommand(LoginMihoyoBBS);
|
||||
RemoveUserCommand = asyncRelayCommandFactory.Create<User>(RemoveUserAsync);
|
||||
CopyCookieCommand = new RelayCommand<User>(CopyCookie);
|
||||
}
|
||||
@@ -78,7 +81,12 @@ internal class UserViewModel : ObservableObject
|
||||
/// <summary>
|
||||
/// 登录米哈游通行证升级到Stoken命令
|
||||
/// </summary>
|
||||
public ICommand UpgradeToStokenCommand { get; }
|
||||
public ICommand LoginMihoyoUserCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录米游社命令
|
||||
/// </summary>
|
||||
public ICommand LoginMihoyoBBSCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 移除用户命令
|
||||
@@ -132,26 +140,14 @@ internal class UserViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpgradeByLoginTicketAsync()
|
||||
private void LoginMihoyoUser()
|
||||
{
|
||||
// Get cookie from user input
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
(bool isOk, Cookie addition) = await new UserAutoCookieDialog(mainWindow).GetInputCookieAsync().ConfigureAwait(false);
|
||||
Ioc.Default.GetRequiredService<INavigationService>().Navigate<LoginMihoyoUserPage>(INavigationAwaiter.Default);
|
||||
}
|
||||
|
||||
// User confirms the input
|
||||
if (isOk)
|
||||
{
|
||||
(UserOptionResult result, string nickname) = await userService.ProcessInputCookieAsync(addition).ConfigureAwait(false);
|
||||
|
||||
if (result == UserOptionResult.Upgraded)
|
||||
{
|
||||
infoBarService.Information($"用户 [{nickname}] 的 Cookie 升级成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning("请先添加对应用户的米游社Cookie");
|
||||
}
|
||||
}
|
||||
private void LoginMihoyoBBS()
|
||||
{
|
||||
Ioc.Default.GetRequiredService<INavigationService>().Navigate<LoginMihoyoBBSPage>(INavigationAwaiter.Default);
|
||||
}
|
||||
|
||||
private async Task RemoveUserAsync(User? user)
|
||||
|
||||
@@ -11,10 +11,10 @@ public class RankInfo
|
||||
/// <summary>
|
||||
/// 造成伤害
|
||||
/// </summary>
|
||||
public ItemRate<int, double> Damage { get; set; } = default!;
|
||||
public RankValue Damage { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 受到伤害
|
||||
/// </summary>
|
||||
public ItemRate<int, double> TakeDamage { get; set; } = default!;
|
||||
}
|
||||
public RankValue TakeDamage { get; set; } = default!;
|
||||
}
|
||||
35
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/RankValue.cs
Normal file
35
src/Snap.Hutao/Snap.Hutao/Web/Hutao/Model/RankValue.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 伤害值
|
||||
/// </summary>
|
||||
public class RankValue : ItemRate<int, double>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的伤害值
|
||||
/// </summary>
|
||||
/// <param name="item">物品</param>
|
||||
/// <param name="value">伤害</param>
|
||||
/// <param name="rate">率</param>
|
||||
/// <param name="rateOnAvatar">角色率</param>
|
||||
[JsonConstructor]
|
||||
public RankValue(int item, int value, double rate, double rateOnAvatar)
|
||||
: base(item, rate)
|
||||
{
|
||||
Value = value;
|
||||
RateOnAvatar = rateOnAvatar;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 伤害值
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色比率
|
||||
/// </summary>
|
||||
public double RateOnAvatar { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user