This commit is contained in:
Lightczx
2023-08-01 22:20:50 +08:00
parent 4f6c2905d2
commit a69ae12e4f
19 changed files with 229 additions and 174 deletions

View File

@@ -81,6 +81,12 @@
<x:String x:Key="UI_EmotionIcon250">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon250.png</x:String> <x:String x:Key="UI_EmotionIcon250">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon250.png</x:String>
<x:String x:Key="UI_EmotionIcon272">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon272.png</x:String> <x:String x:Key="UI_EmotionIcon272">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon272.png</x:String>
<x:String x:Key="UI_EmotionIcon293">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon293.png</x:String> <x:String x:Key="UI_EmotionIcon293">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon293.png</x:String>
<!-- 图床 Url -->
<x:String x:Key="UI_ItemIcon_204">https://smms.app/image/x9psnPrcbYoCl6U</x:String>
<x:String x:Key="UI_ItemIcon_210">https://smms.app/image/n4gwxlFGPTX2j8p</x:String>
<x:String x:Key="UI_ItemIcon_220021">https://smms.app/image/kbh1a2YVXpxWuez</x:String>
<x:String x:Key="UI_Icon_Intee_Explore_1">https://smms.app/image/zJ4UYqKiD6uQlLc</x:String>
<x:String x:Key="UI_MarkQuest_Events_Proce">https://smms.app/image/DQyTF3rv4aA8MZV</x:String>
<!-- Converters --> <!-- Converters -->
<cwuc:BoolNegationConverter x:Key="BoolNegationConverter"/> <cwuc:BoolNegationConverter x:Key="BoolNegationConverter"/>
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/> <cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>

View File

@@ -105,17 +105,19 @@ internal static partial class EnumerableExtension
return results; return results;
} }
/// <summary>
/// 按元素的键排序
/// </summary>
/// <typeparam name="TSource">元素类型</typeparam>
/// <typeparam name="TKey">键类型</typeparam>
/// <param name="list">列表</param>
/// <param name="keySelector">键选择器</param>
[MethodImpl(MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void SortBy<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector) public static List<TSource> SortBy<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector)
where TKey : IComparable where TKey : IComparable
{ {
list.Sort((left, right) => keySelector(left).CompareTo(keySelector(right))); list.Sort((left, right) => keySelector(left).CompareTo(keySelector(right)));
return list;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static List<TSource> SortByDescending<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector)
where TKey : IComparable
{
list.Sort((left, right) => keySelector(right).CompareTo(keySelector(left)));
return list;
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -141,7 +141,7 @@ internal sealed class DailyNoteNotificationOperation
{ {
notifyInfos.Add(new( notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierResin, SH.ServiceDailyNoteNotifierResin,
Web.Hoyolab.OssImages.UIItemIcon210, Web.Hoyolab.Images.UIItemIcon210,
$"{entry.DailyNote.CurrentResin}", $"{entry.DailyNote.CurrentResin}",
string.Format(SH.ServiceDailyNoteNotifierResinCurrent, entry.DailyNote.CurrentResin))); string.Format(SH.ServiceDailyNoteNotifierResinCurrent, entry.DailyNote.CurrentResin)));
entry.ResinNotifySuppressed = true; entry.ResinNotifySuppressed = true;
@@ -158,7 +158,7 @@ internal sealed class DailyNoteNotificationOperation
{ {
notifyInfos.Add(new( notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierHomeCoin, SH.ServiceDailyNoteNotifierHomeCoin,
Web.Hoyolab.OssImages.UIItemIcon204, Web.Hoyolab.Images.UIItemIcon204,
$"{entry.DailyNote.CurrentHomeCoin}", $"{entry.DailyNote.CurrentHomeCoin}",
string.Format(SH.ServiceDailyNoteNotifierHomeCoinCurrent, entry.DailyNote.CurrentHomeCoin))); string.Format(SH.ServiceDailyNoteNotifierHomeCoinCurrent, entry.DailyNote.CurrentHomeCoin)));
entry.HomeCoinNotifySuppressed = true; entry.HomeCoinNotifySuppressed = true;
@@ -175,7 +175,7 @@ internal sealed class DailyNoteNotificationOperation
{ {
notifyInfos.Add(new( notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierDailyTask, SH.ServiceDailyNoteNotifierDailyTask,
Web.Hoyolab.OssImages.UIMarkQuestEventsProce, Web.Hoyolab.Images.UIMarkQuestEventsProce,
SH.ServiceDailyNoteNotifierDailyTaskHint, SH.ServiceDailyNoteNotifierDailyTaskHint,
entry.DailyNote.ExtraTaskRewardDescription)); entry.DailyNote.ExtraTaskRewardDescription));
entry.DailyTaskNotifySuppressed = true; entry.DailyTaskNotifySuppressed = true;
@@ -192,7 +192,7 @@ internal sealed class DailyNoteNotificationOperation
{ {
notifyInfos.Add(new( notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierTransformer, SH.ServiceDailyNoteNotifierTransformer,
Web.Hoyolab.OssImages.UIItemIcon220021, Web.Hoyolab.Images.UIItemIcon220021,
SH.ServiceDailyNoteNotifierTransformerAdaptiveHint, SH.ServiceDailyNoteNotifierTransformerAdaptiveHint,
SH.ServiceDailyNoteNotifierTransformerHint)); SH.ServiceDailyNoteNotifierTransformerHint));
entry.TransformerNotifySuppressed = true; entry.TransformerNotifySuppressed = true;
@@ -209,7 +209,7 @@ internal sealed class DailyNoteNotificationOperation
{ {
notifyInfos.Add(new( notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierExpedition, SH.ServiceDailyNoteNotifierExpedition,
Web.Hoyolab.OssImages.UIIconInteeExplore1, Web.Hoyolab.Images.UIIconInteeExplore1,
SH.ServiceDailyNoteNotifierExpeditionAdaptiveHint, SH.ServiceDailyNoteNotifierExpeditionAdaptiveHint,
SH.ServiceDailyNoteNotifierExpeditionHint)); SH.ServiceDailyNoteNotifierExpeditionHint));
entry.ExpeditionNotifySuppressed = true; entry.ExpeditionNotifySuppressed = true;

View File

@@ -11,6 +11,9 @@ namespace Snap.Hutao.Service.Game;
[HighQuality] [HighQuality]
internal readonly struct ChannelOptions internal readonly struct ChannelOptions
{ {
public const string ChannelName = "channel";
public const string SubChannelName = "sub_channel";
/// <summary> /// <summary>
/// 通道 /// 通道
/// </summary> /// </summary>

View File

@@ -0,0 +1,62 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Game;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IGameDbService))]
internal sealed partial class GameDbService : IGameDbService
{
private readonly IServiceProvider serviceProvider;
public ObservableCollection<GameAccount> GetGameAccountCollection()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.GameAccounts.AsNoTracking().ToObservableCollection();
}
}
public async ValueTask AddGameAccountAsync(GameAccount gameAccount)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GameAccounts.AddAndSaveAsync(gameAccount).ConfigureAwait(false);
}
}
public async ValueTask UpdateGameAccountAsync(GameAccount gameAccount)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GameAccounts.UpdateAndSaveAsync(gameAccount).ConfigureAwait(false);
}
}
public void UpdateGameAccount(GameAccount gameAccount)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.GameAccounts.UpdateAndSave(gameAccount);
}
}
public async ValueTask DeleteGameAccountByIdAsync(Guid id)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GameAccounts.ExecuteDeleteWhereAsync(a => a.InnerId == id).ConfigureAwait(false);
}
}
}

View File

@@ -1,13 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core; using Snap.Hutao.Core;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO.Ini; using Snap.Hutao.Core.IO.Ini;
using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service.Game.Locator; using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Service.Game.Package; using Snap.Hutao.Service.Game.Package;
using Snap.Hutao.View.Dialog; using Snap.Hutao.View.Dialog;
@@ -32,7 +29,7 @@ internal sealed partial class GameService : IGameService
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly IGameDbService gameDbService; private readonly IGameDbService gameDbService;
private readonly LaunchOptions launchOptions; private readonly LaunchOptions launchOptions;
private readonly RuntimeOptions hutaoOptions; private readonly RuntimeOptions runtimeOptions;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly AppOptions appOptions; private readonly AppOptions appOptions;
@@ -48,48 +45,45 @@ internal sealed partial class GameService : IGameService
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask<ValueResult<bool, string>> GetGamePathAsync() public async ValueTask<ValueResult<bool, string>> GetGamePathAsync()
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) // Cannot find in setting
if (string.IsNullOrEmpty(appOptions.GamePath))
{ {
// Cannot find in setting IGameLocatorFactory locatorFactory = serviceProvider.GetRequiredService<IGameLocatorFactory>();
if (string.IsNullOrEmpty(appOptions.GamePath))
{
IGameLocatorFactory locatorFactory = scope.ServiceProvider.GetRequiredService<IGameLocatorFactory>();
// Try locate by unity log // Try locate by unity log
ValueResult<bool, string> result = await locatorFactory ValueResult<bool, string> result = await locatorFactory
.Create(GameLocationSource.UnityLog) .Create(GameLocationSource.UnityLog)
.LocateGamePathAsync()
.ConfigureAwait(false);
if (!result.IsOk)
{
// Try locate by registry
result = await locatorFactory
.Create(GameLocationSource.Registry)
.LocateGamePathAsync() .LocateGamePathAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
if (!result.IsOk)
{
// Try locate by registry
result = await locatorFactory
.Create(GameLocationSource.Registry)
.LocateGamePathAsync()
.ConfigureAwait(false);
}
if (result.IsOk)
{
// Save result.
appOptions.GamePath = result.Value;
}
else
{
return new(false, SH.ServiceGamePathLocateFailed);
}
} }
if (!string.IsNullOrEmpty(appOptions.GamePath)) if (result.IsOk)
{ {
return new(true, appOptions.GamePath); // Save result.
appOptions.GamePath = result.Value;
} }
else else
{ {
return new(false, null!); return new(false, SH.ServiceGamePathLocateFailed);
} }
} }
if (!string.IsNullOrEmpty(appOptions.GamePath))
{
return new(true, appOptions.GamePath);
}
else
{
return new(false, null!);
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -107,8 +101,8 @@ internal sealed partial class GameService : IGameService
using (FileStream stream = File.OpenRead(configPath)) using (FileStream stream = File.OpenRead(configPath))
{ {
List<IniParameter> parameters = IniSerializer.Deserialize(stream).OfType<IniParameter>().ToList(); List<IniParameter> parameters = IniSerializer.Deserialize(stream).OfType<IniParameter>().ToList();
string? channel = parameters.FirstOrDefault(p => p.Key == "channel")?.Value; string? channel = parameters.FirstOrDefault(p => p.Key == ChannelOptions.ChannelName)?.Value;
string? subChannel = parameters.FirstOrDefault(p => p.Key == "sub_channel")?.Value; string? subChannel = parameters.FirstOrDefault(p => p.Key == ChannelOptions.SubChannelName)?.Value;
return new(channel, subChannel, isOversea); return new(channel, subChannel, isOversea);
} }
@@ -178,14 +172,10 @@ internal sealed partial class GameService : IGameService
string gameFileName = Path.GetFileName(gamePath); string gameFileName = Path.GetFileName(gamePath);
progress.Report(new(SH.ServiceGameEnsureGameResourceQueryResourceInformation)); progress.Report(new(SH.ServiceGameEnsureGameResourceQueryResourceInformation));
Response<GameResource> response; Response<GameResource> response = await serviceProvider
using (IServiceScope scope = serviceProvider.CreateScope()) .GetRequiredService<ResourceClient>()
{ .GetResourceAsync(launchScheme)
response = await scope.ServiceProvider .ConfigureAwait(false);
.GetRequiredService<ResourceClient>()
.GetResourceAsync(launchScheme)
.ConfigureAwait(false);
}
if (response.IsOk()) if (response.IsOk())
{ {
@@ -257,7 +247,7 @@ internal sealed partial class GameService : IGameService
return; return;
} }
Process game = ProcessInterop.PrepareGameProcess(launchOptions, gamePath); Process game = ProcessInterop.InitializeGameProcess(launchOptions, gamePath);
try try
{ {
@@ -265,7 +255,7 @@ internal sealed partial class GameService : IGameService
game.Start(); game.Start();
bool isAdvancedOptionsAllowed = hutaoOptions.IsElevated && appOptions.IsAdvancedLaunchOptionsEnabled; bool isAdvancedOptionsAllowed = runtimeOptions.IsElevated && appOptions.IsAdvancedLaunchOptionsEnabled;
if (isAdvancedOptionsAllowed && launchOptions.MultipleInstances && !isFirstInstance) if (isAdvancedOptionsAllowed && launchOptions.MultipleInstances && !isFirstInstance)
{ {
ProcessInterop.DisableProtection(game, gamePath); ProcessInterop.DisableProtection(game, gamePath);
@@ -317,14 +307,7 @@ internal sealed partial class GameService : IGameService
// sync database // sync database
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
using (IServiceScope scope = serviceProvider.CreateScope()) await gameDbService.AddGameAccountAsync(account).ConfigureAwait(false);
{
await scope.ServiceProvider
.GetRequiredService<AppDbContext>()
.GameAccounts
.AddAndSaveAsync(account)
.ConfigureAwait(false);
}
// sync cache // sync cache
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
@@ -365,11 +348,8 @@ internal sealed partial class GameService : IGameService
/// <inheritdoc/> /// <inheritdoc/>
public void AttachGameAccountToUid(GameAccount gameAccount, string uid) public void AttachGameAccountToUid(GameAccount gameAccount, string uid)
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) gameAccount.UpdateAttachUid(uid);
{ gameDbService.UpdateGameAccount(gameAccount);
gameAccount.UpdateAttachUid(uid);
scope.ServiceProvider.GetRequiredService<AppDbContext>().GameAccounts.UpdateAndSave(gameAccount);
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -385,11 +365,7 @@ internal sealed partial class GameService : IGameService
// sync database // sync database
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
using (IServiceScope scope = serviceProvider.CreateScope()) await gameDbService.UpdateGameAccountAsync(gameAccount).ConfigureAwait(false);
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.GameAccounts.UpdateAndSaveAsync(gameAccount).ConfigureAwait(false);
}
} }
} }
@@ -400,10 +376,7 @@ internal sealed partial class GameService : IGameService
gameAccounts!.Remove(gameAccount); gameAccounts!.Remove(gameAccount);
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
using (IServiceScope scope = serviceProvider.CreateScope()) await gameDbService.DeleteGameAccountByIdAsync(gameAccount.InnerId).ConfigureAwait(false);
{
await scope.ServiceProvider.GetRequiredService<AppDbContext>().GameAccounts.RemoveAndSaveAsync(gameAccount).ConfigureAwait(false);
}
} }
private static bool LaunchSchemeMatchesExecutable(LaunchScheme launchScheme, string gameFileName) private static bool LaunchSchemeMatchesExecutable(LaunchScheme launchScheme, string gameFileName)
@@ -415,25 +388,4 @@ internal sealed partial class GameService : IGameService
_ => false, _ => false,
}; };
} }
}
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IGameDbService))]
internal sealed partial class GameDbService : IGameDbService
{
private readonly IServiceProvider serviceProvider;
public ObservableCollection<GameAccount> GetGameAccountCollection()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.GameAccounts.AsNoTracking().ToObservableCollection();
}
}
}
internal interface IGameDbService
{
ObservableCollection<GameAccount> GetGameAccountCollection();
} }

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Game;
internal interface IGameDbService
{
ValueTask AddGameAccountAsync(GameAccount gameAccount);
ValueTask DeleteGameAccountByIdAsync(Guid id);
ObservableCollection<GameAccount> GetGameAccountCollection();
void UpdateGameAccount(GameAccount gameAccount);
ValueTask UpdateGameAccountAsync(GameAccount gameAccount);
}

View File

@@ -43,16 +43,7 @@ internal sealed class LaunchOptions : DbStoreOptions
primaryScreenWidth = primaryRect.Width; primaryScreenWidth = primaryRect.Width;
primaryScreenHeight = primaryRect.Height; primaryScreenHeight = primaryRect.Height;
// This list can't use foreach InitializeMonitors(Monitors);
// https://github.com/microsoft/microsoft-ui-xaml/issues/6454
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
for (int i = 0; i < displayAreas.Count; i++)
{
DisplayArea displayArea = displayAreas[i];
int index = i + 1;
Monitors.Add(new($"{displayArea.DisplayId.Value:X8}:{index}", index));
}
InitializeScreenFps(out primaryScreenFps); InitializeScreenFps(out primaryScreenFps);
} }
@@ -168,6 +159,19 @@ internal sealed class LaunchOptions : DbStoreOptions
set => SetOption(ref multipleInstances, SettingEntry.MultipleInstances, value); set => SetOption(ref multipleInstances, SettingEntry.MultipleInstances, value);
} }
private static void InitializeMonitors(List<NameValue<int>> monitors)
{
// This list can't use foreach
// https://github.com/microsoft/microsoft-ui-xaml/issues/6454
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
for (int i = 0; i < displayAreas.Count; i++)
{
DisplayArea displayArea = displayAreas[i];
int index = i + 1;
monitors.Add(new($"{displayArea.DisplayId.Value:X8}:{index}", index));
}
}
private static void InitializeScreenFps(out int fps) private static void InitializeScreenFps(out int fps)
{ {
HDC hDC = GetDC(HWND.Null); HDC hDC = GetDC(HWND.Null);

View File

@@ -24,7 +24,7 @@ internal static class ProcessInterop
/// <param name="options">启动选项</param> /// <param name="options">启动选项</param>
/// <param name="gamePath">游戏路径</param> /// <param name="gamePath">游戏路径</param>
/// <returns>初始化后的游戏进程</returns> /// <returns>初始化后的游戏进程</returns>
public static Process PrepareGameProcess(LaunchOptions options, string gamePath) public static Process InitializeGameProcess(LaunchOptions options, string gamePath)
{ {
// https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html // https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html
string commandLine = new CommandLineBuilder() string commandLine = new CommandLineBuilder()

View File

@@ -95,21 +95,7 @@ internal sealed partial class HutaoCache : IHutaoCache
Dictionary<AvatarId, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false); Dictionary<AvatarId, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
Dictionary<WeaponId, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false); Dictionary<WeaponId, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
Dictionary<EquipAffixId, Model.Metadata.Reliquary.ReliquarySet> idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false); Dictionary<EquipAffixId, Model.Metadata.Reliquary.ReliquarySet> idReliquarySetMap = await metadataService.GetEquipAffixIdToReliquarySetMapAsync().ConfigureAwait(false);
await AvatarCollocationsAsync(idAvatarMap, idWeaponMap, idReliquarySetMap).ConfigureAwait(false);
List<AvatarCollocation> avatarCollocationsRaw;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false);
}
AvatarCollocations = avatarCollocationsRaw.Select(co => new AvatarCollocationView()
{
AvatarId = co.AvatarId,
Avatars = co.Avatars.Select(a => new AvatarView(idAvatarMap[a.Item], a.Rate)).ToList(),
Weapons = co.Weapons.Select(w => new WeaponView(idWeaponMap[w.Item], w.Rate)).ToList(),
ReliquarySets = co.Reliquaries.Select(r => new ReliquarySetView(r, idReliquarySetMap)).ToList(),
}).ToList();
wikiAvatarViewModelTaskSource.TrySetResult(true); wikiAvatarViewModelTaskSource.TrySetResult(true);
return true; return true;
@@ -131,19 +117,7 @@ internal sealed partial class HutaoCache : IHutaoCache
if (await metadataService.InitializeAsync().ConfigureAwait(false)) if (await metadataService.InitializeAsync().ConfigureAwait(false))
{ {
Dictionary<AvatarId, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false); Dictionary<AvatarId, Avatar> idAvatarMap = await GetIdAvatarMapExtendedAsync().ConfigureAwait(false);
await WeaponCollocationsAsync(idAvatarMap).ConfigureAwait(false);
List<WeaponCollocation> weaponCollocationsRaw;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
weaponCollocationsRaw = await hutaoService.GetWeaponCollocationsAsync().ConfigureAwait(false);
}
WeaponCollocations = weaponCollocationsRaw.Select(co => new WeaponCollocationView()
{
WeaponId = co.WeaponId,
Avatars = co.Avatars.Select(a => new AvatarView(idAvatarMap[a.Item], a.Rate)).ToList(),
}).ToList();
wikiWeaponViewModelTaskSource.TrySetResult(true); wikiWeaponViewModelTaskSource.TrySetResult(true);
return true; return true;
@@ -164,6 +138,41 @@ internal sealed partial class HutaoCache : IHutaoCache
return idAvatarExtendedMap; return idAvatarExtendedMap;
} }
private async ValueTask AvatarCollocationsAsync(Dictionary<AvatarId, Avatar> idAvatarMap, Dictionary<WeaponId, Weapon> idWeaponMap, Dictionary<EquipAffixId, Model.Metadata.Reliquary.ReliquarySet> idReliquarySetMap)
{
List<AvatarCollocation> avatarCollocationsRaw;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
avatarCollocationsRaw = await hutaoService.GetAvatarCollocationsAsync().ConfigureAwait(false);
}
AvatarCollocations = avatarCollocationsRaw.SelectList(co => new AvatarCollocationView()
{
AvatarId = co.AvatarId,
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
Weapons = co.Weapons.SelectList(w => new WeaponView(idWeaponMap[w.Item], w.Rate)),
ReliquarySets = co.Reliquaries.SelectList(r => new ReliquarySetView(r, idReliquarySetMap)),
});
}
private async ValueTask WeaponCollocationsAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
{
List<WeaponCollocation> weaponCollocationsRaw;
using (IServiceScope scope = serviceProvider.CreateScope())
{
IHutaoService hutaoService = scope.ServiceProvider.GetRequiredService<IHutaoService>();
weaponCollocationsRaw = await hutaoService.GetWeaponCollocationsAsync().ConfigureAwait(false);
}
WeaponCollocations = weaponCollocationsRaw.SelectList(co => new WeaponCollocationView()
{
WeaponId = co.WeaponId,
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
});
}
[SuppressMessage("", "SH003")]
private async Task AvatarAppearanceRankAsync(Dictionary<AvatarId, Avatar> idAvatarMap) private async Task AvatarAppearanceRankAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
{ {
List<AvatarAppearanceRank> avatarAppearanceRanksRaw; List<AvatarAppearanceRank> avatarAppearanceRanksRaw;
@@ -173,13 +182,14 @@ internal sealed partial class HutaoCache : IHutaoCache
avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false); avatarAppearanceRanksRaw = await hutaoService.GetAvatarAppearanceRanksAsync().ConfigureAwait(false);
} }
AvatarAppearanceRanks = avatarAppearanceRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new AvatarRankView AvatarAppearanceRanks = avatarAppearanceRanksRaw.SortByDescending(r => r.Floor).SelectList(rank => new AvatarRankView
{ {
Floor = string.Format(SH.ModelBindingHutaoComplexRankFloor, rank.Floor), Floor = string.Format(SH.ModelBindingHutaoComplexRankFloor, rank.Floor),
Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new AvatarView(idAvatarMap[rank.Item], rank.Rate)).ToList(), Avatars = rank.Ranks.SortByDescending(r => r.Rate).SelectList(rank => new AvatarView(idAvatarMap[rank.Item], rank.Rate)),
}).ToList(); });
} }
[SuppressMessage("", "SH003")]
private async Task AvatarUsageRanksAsync(Dictionary<AvatarId, Avatar> idAvatarMap) private async Task AvatarUsageRanksAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
{ {
List<AvatarUsageRank> avatarUsageRanksRaw; List<AvatarUsageRank> avatarUsageRanksRaw;
@@ -189,13 +199,14 @@ internal sealed partial class HutaoCache : IHutaoCache
avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false); avatarUsageRanksRaw = await hutaoService.GetAvatarUsageRanksAsync().ConfigureAwait(false);
} }
AvatarUsageRanks = avatarUsageRanksRaw.OrderByDescending(r => r.Floor).Select(rank => new AvatarRankView AvatarUsageRanks = avatarUsageRanksRaw.SortByDescending(r => r.Floor).SelectList(rank => new AvatarRankView
{ {
Floor = string.Format(SH.ModelBindingHutaoComplexRankFloor, rank.Floor), Floor = string.Format(SH.ModelBindingHutaoComplexRankFloor, rank.Floor),
Avatars = rank.Ranks.OrderByDescending(r => r.Rate).Select(rank => new AvatarView(idAvatarMap[rank.Item], rank.Rate)).ToList(), Avatars = rank.Ranks.SortByDescending(r => r.Rate).SelectList(rank => new AvatarView(idAvatarMap[rank.Item], rank.Rate)),
}).ToList(); });
} }
[SuppressMessage("", "SH003")]
private async Task AvatarConstellationInfosAsync(Dictionary<AvatarId, Avatar> idAvatarMap) private async Task AvatarConstellationInfosAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
{ {
List<AvatarConstellationInfo> avatarConstellationInfosRaw; List<AvatarConstellationInfo> avatarConstellationInfosRaw;
@@ -205,12 +216,13 @@ internal sealed partial class HutaoCache : IHutaoCache
avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false); avatarConstellationInfosRaw = await hutaoService.GetAvatarConstellationInfosAsync().ConfigureAwait(false);
} }
AvatarConstellationInfos = avatarConstellationInfosRaw.OrderBy(i => i.HoldingRate).Select(info => AvatarConstellationInfos = avatarConstellationInfosRaw.SortBy(i => i.HoldingRate).SelectList(info =>
{ {
return new AvatarConstellationInfoView(idAvatarMap[info.AvatarId], info.HoldingRate, info.Constellations.SelectList(x => x.Rate)); return new AvatarConstellationInfoView(idAvatarMap[info.AvatarId], info.HoldingRate, info.Constellations.SelectList(x => x.Rate));
}).ToList(); });
} }
[SuppressMessage("", "SH003")]
private async Task TeamAppearancesAsync(Dictionary<AvatarId, Avatar> idAvatarMap) private async Task TeamAppearancesAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
{ {
List<TeamAppearance> teamAppearancesRaw; List<TeamAppearance> teamAppearancesRaw;
@@ -220,9 +232,10 @@ internal sealed partial class HutaoCache : IHutaoCache
teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false); teamAppearancesRaw = await hutaoService.GetTeamAppearancesAsync().ConfigureAwait(false);
} }
TeamAppearances = teamAppearancesRaw.OrderByDescending(t => t.Floor).Select(team => new TeamAppearanceView(team, idAvatarMap)).ToList(); TeamAppearances = teamAppearancesRaw.SortByDescending(t => t.Floor).SelectList(team => new TeamAppearanceView(team, idAvatarMap));
} }
[SuppressMessage("", "SH003")]
private async Task OverviewAsync() private async Task OverviewAsync()
{ {
using (IServiceScope scope = serviceProvider.CreateScope()) using (IServiceScope scope = serviceProvider.CreateScope())

View File

@@ -108,8 +108,6 @@ internal sealed partial class HutaoService : IHutaoService
await appDbContext.ObjectCache.AddAndSaveAsync(new() await appDbContext.ObjectCache.AddAndSaveAsync(new()
{ {
Key = key, Key = key,
// We hold the cache for 4 hours
ExpireTime = DateTimeOffset.Now.Add(CacheExpireTime), ExpireTime = DateTimeOffset.Now.Add(CacheExpireTime),
Value = JsonSerializer.Serialize(data, options), Value = JsonSerializer.Serialize(data, options),
}).ConfigureAwait(false); }).ConfigureAwait(false);

View File

@@ -86,7 +86,7 @@ internal sealed class HutaoUserOptions : ObservableObject, IOptions<HutaoUserOpt
public void UpdateUserInfo(UserInfo userInfo) public void UpdateUserInfo(UserInfo userInfo)
{ {
IsLicensedDeveloper = userInfo.IsLicensedDeveloper; IsLicensedDeveloper = userInfo.IsLicensedDeveloper;
IsCloudServiceAllowed = userInfo.GachaLogExpireAt > DateTimeOffset.Now;
GachaLogExpireAt = string.Format(Regex.Unescape(SH.ServiceHutaoUserGachaLogExpiredAt), userInfo.GachaLogExpireAt); GachaLogExpireAt = string.Format(Regex.Unescape(SH.ServiceHutaoUserGachaLogExpiredAt), userInfo.GachaLogExpireAt);
IsCloudServiceAllowed = IsLicensedDeveloper || userInfo.GachaLogExpireAt > DateTimeOffset.Now;
} }
} }

View File

@@ -97,11 +97,8 @@
<None Remove="Resource\Icon\UI_Icon_None.png" /> <None Remove="Resource\Icon\UI_Icon_None.png" />
<None Remove="Resource\Icon\UI_Icon_Tower_Star.png" /> <None Remove="Resource\Icon\UI_Icon_Tower_Star.png" />
<None Remove="Resource\Icon\UI_ItemIcon_201.png" /> <None Remove="Resource\Icon\UI_ItemIcon_201.png" />
<None Remove="Resource\Icon\UI_ItemIcon_204.png" />
<None Remove="Resource\Icon\UI_ItemIcon_210.png" /> <None Remove="Resource\Icon\UI_ItemIcon_210.png" />
<None Remove="Resource\Icon\UI_ItemIcon_220021.png" />
<None Remove="Resource\Icon\UI_MarkCustom_TagMonster.png" /> <None Remove="Resource\Icon\UI_MarkCustom_TagMonster.png" />
<None Remove="Resource\Icon\UI_MarkQuest_Events_Proce.png" />
<None Remove="Resource\Icon\UI_MarkTower.png" /> <None Remove="Resource\Icon\UI_MarkTower.png" />
<None Remove="Resource\Icon\UI_MarkTower_Tower.png" /> <None Remove="Resource\Icon\UI_MarkTower_Tower.png" />
<None Remove="Resource\Segoe Fluent Icons.ttf" /> <None Remove="Resource\Segoe Fluent Icons.ttf" />
@@ -227,11 +224,8 @@
<Content Include="Resource\Icon\UI_Icon_None.png" /> <Content Include="Resource\Icon\UI_Icon_None.png" />
<Content Include="Resource\Icon\UI_Icon_Tower_Star.png" /> <Content Include="Resource\Icon\UI_Icon_Tower_Star.png" />
<Content Include="Resource\Icon\UI_ItemIcon_201.png" /> <Content Include="Resource\Icon\UI_ItemIcon_201.png" />
<Content Include="Resource\Icon\UI_ItemIcon_204.png" />
<Content Include="Resource\Icon\UI_ItemIcon_210.png" /> <Content Include="Resource\Icon\UI_ItemIcon_210.png" />
<Content Include="Resource\Icon\UI_ItemIcon_220021.png" />
<Content Include="Resource\Icon\UI_MarkCustom_TagMonster.png" /> <Content Include="Resource\Icon\UI_MarkCustom_TagMonster.png" />
<Content Include="Resource\Icon\UI_MarkQuest_Events_Proce.png" />
<Content Include="Resource\Icon\UI_MarkTower.png" /> <Content Include="Resource\Icon\UI_MarkTower.png" />
<Content Include="Resource\Icon\UI_MarkTower_Tower.png" /> <Content Include="Resource\Icon\UI_MarkTower_Tower.png" />
<Content Include="Resource\WelcomeView_Background.png" /> <Content Include="Resource\WelcomeView_Background.png" />

View File

@@ -6,6 +6,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity" xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shcb="using:Snap.Hutao.Control.Behavior" xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shvc="using:Snap.Hutao.View.Control" xmlns:shvc="using:Snap.Hutao.View.Control"
xmlns:shvd="using:Snap.Hutao.ViewModel.DailyNote" xmlns:shvd="using:Snap.Hutao.ViewModel.DailyNote"
Padding="0" Padding="0"
@@ -75,7 +76,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Border Style="{StaticResource BorderCardStyle}"> <Border Style="{StaticResource BorderCardStyle}">
<StackPanel VerticalAlignment="Center"> <StackPanel VerticalAlignment="Center">
<Image Width="32" Source="ms-appx:///Resource/Icon/UI_ItemIcon_204.png"/> <shci:CachedImage Width="32" Source="{StaticResource UI_ItemIcon_204}"/>
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"

View File

@@ -229,7 +229,7 @@
Width="40" Width="40"
Height="40" Height="40"
VerticalAlignment="Center"> VerticalAlignment="Center">
<Image Width="32" Source="ms-appx:///Resource/Icon/UI_ItemIcon_204.png"/> <shci:CachedImage Width="32" Source="{StaticResource UI_ItemIcon_204}"/>
<ProgressRing <ProgressRing
Width="40" Width="40"
Height="40" Height="40"

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab;
internal static class Images
{
public const string UIItemIcon204 = $"https://smms.app/image/x9psnPrcbYoCl6U";
public const string UIItemIcon210 = $"https://smms.app/image/n4gwxlFGPTX2j8p";
public const string UIItemIcon220021 = $"https://smms.app/image/kbh1a2YVXpxWuez";
public const string UIIconInteeExplore1 = $"https://smms.app/image/zJ4UYqKiD6uQlLc";
public const string UIMarkQuestEventsProce = $"https://smms.app/image/DQyTF3rv4aA8MZV";
}

View File

@@ -1,13 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab;
internal static class OssImages
{
public const string UIMarkQuestEventsProce = "https://upload-bbs.miyoushe.com/upload/2023/07/26/184872832/4b9ba51caa13e5516d507d64c7234b0c_7636696921807680614.png";
public const string UIItemIcon220021 = "https://upload-bbs.miyoushe.com/upload/2023/07/26/184872832/7cd02abbf20727ec4fd0dae777b65748_6274739470729303254.png";
public const string UIItemIcon210 = "https://upload-bbs.miyoushe.com/upload/2023/07/26/184872832/75dbb40f46380efbdbc23bd3a251c83a_538508470291955954.png";
public const string UIItemIcon204 = "https://upload-bbs.miyoushe.com/upload/2023/07/26/184872832/4a3cbdd778648b8aee5c4ebe9672c68b_5158859174407167834.png";
public const string UIIconInteeExplore1 = "https://upload-bbs.miyoushe.com/upload/2023/07/26/184872832/830fcd53539be4aa7faa02391f5384c5_8996801294815343498.png";
}