diff --git a/README.md b/README.md index bf7b4077..193d0bd7 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # [Snap.Hutao](https://hut.ao) + +![](https://repository-images.githubusercontent.com/482734649/5f8cf574-2ef0-43e9-aa8d-6cf094b54dd9) + > 唷,找本堂主有何贵干呀? ![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg) # 特别感谢 -### 原神组织与个人 - * [HolographicHat](https://github.com/HolographicHat) * [UIGF organization](https://uigf.org) diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml index b8ea48cd..53814262 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml @@ -11,6 +11,7 @@ + @@ -27,8 +28,6 @@ - - ms-appx:///Resource/Font/Segoe Fluent Icons.ttf#Segoe Fluent Icons 6,16,16,16 16,0,0,0 @@ -90,6 +89,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs index 074aec4f..47f85863 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Core.Logging; +using System.Collections.Concurrent; using System.Collections.Immutable; using System.IO; using System.Net; @@ -24,7 +25,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation { private const string CacheFolderName = nameof(ImageCache); - private static readonly ImmutableDictionary RetryCountToDelay = new Dictionary() + private static readonly Dictionary RetryCountToDelay = new Dictionary() { [0] = TimeSpan.FromSeconds(4), [1] = TimeSpan.FromSeconds(16), @@ -32,13 +33,13 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation [3] = TimeSpan.FromSeconds(4), [4] = TimeSpan.FromSeconds(16), [5] = TimeSpan.FromSeconds(64), - }.ToImmutableDictionary(); + }; private readonly ILogger logger; - - // violate di rule private readonly HttpClient httpClient; + private readonly ConcurrentDictionary concurrentTasks = new(); + private string? baseFolder; private string? cacheFolder; @@ -100,11 +101,30 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation /// public async Task GetFileFromCacheAsync(Uri uri) { - string filePath = Path.Combine(GetCacheFolder(), GetCacheFileName(uri)); + string fileName = GetCacheFileName(uri); + string filePath = Path.Combine(GetCacheFolder(), fileName); if (!File.Exists(filePath) || new FileInfo(filePath).Length == 0) { - await DownloadFileAsync(uri, filePath).ConfigureAwait(false); + TaskCompletionSource taskCompletionSource = new(); + try + { + if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task)) + { + await DownloadFileAsync(uri, filePath).ConfigureAwait(false); + } + else + { + if (concurrentTasks.TryGetValue(fileName, out Task? task)) + { + await task.ConfigureAwait(false); + } + } + } + finally + { + taskCompletionSource.TrySetResult(); + } } return filePath; @@ -191,7 +211,7 @@ public class ImageCache : IImageCache, IImageCacheFilePathOperation if (retryCount == 3) { - uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao, }.Uri; + uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao }.Uri; } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceScopeExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceScopeExtension.cs index 97e63433..f52fd970 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceScopeExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceScopeExtension.cs @@ -10,7 +10,8 @@ namespace Snap.Hutao.Core.DependencyInjection; /// public static class ServiceScopeExtension { - private static IServiceScope? scopeReference; + // Allow GC to Collect the IServiceScope + private static readonly WeakReference ScopeReference = new(null!); /// /// 追踪服务范围 @@ -19,7 +20,7 @@ public static class ServiceScopeExtension public static void Track(this IServiceScope scope) { DisposeLast(); - scopeReference = scope; + ScopeReference.SetTarget(scope); } /// @@ -27,6 +28,9 @@ public static class ServiceScopeExtension /// public static void DisposeLast() { - scopeReference?.Dispose(); + if (ScopeReference.TryGetTarget(out IServiceScope? scope)) + { + scope.Dispose(); + } } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs index 7c3d6d76..1b0a21fa 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionRecorder.cs @@ -45,4 +45,4 @@ internal class ExceptionRecorder { logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message); } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs new file mode 100644 index 00000000..bb4c8c31 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/UserdataCorruptedException.cs @@ -0,0 +1,20 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.ExceptionService; + +/// +/// 用户数据损坏异常 +/// +internal class UserdataCorruptedException : Exception +{ + /// + /// 构造一个新的用户数据损坏异常 + /// + /// 消息 + /// 内部错误 + public UserdataCorruptedException(string message, Exception innerException) + : base($"用户数据已损坏: {message}", innerException) + { + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ScheduleTaskHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ScheduleTaskHelper.cs index 7c460e34..8cf91b96 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ScheduleTaskHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ScheduleTaskHelper.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.Win32.TaskScheduler; +using System.IO; using System.Runtime.InteropServices; using SchedulerTask = Microsoft.Win32.TaskScheduler.Task; @@ -23,6 +24,7 @@ internal static class ScheduleTaskHelper { try { + // TODO: 似乎可以不删除任务,直接注册已经包含了更新功能 SchedulerTask? targetTask = TaskService.Instance.GetTask(DailyNoteRefreshTaskName); if (targetTask != null) { @@ -36,12 +38,9 @@ internal static class ScheduleTaskHelper TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task); return true; } - catch (UnauthorizedAccessException) - { - return false; - } - catch (COMException) + catch (Exception ex) { + _ = ex; return false; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs index 0c952a1f..bbc79453 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs @@ -32,4 +32,9 @@ internal static class SettingKeys /// 静态资源合约V2 成就图标与物品图标 /// public const string StaticResourceV2Contract = "StaticResourceV2Contract"; + + /// + /// 静态资源合约V3 刷新 Skill Talent + /// + public const string StaticResourceV3Contract = "StaticResourceV3Contract"; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs index f549a814..01cf3deb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs @@ -39,6 +39,13 @@ internal static class ThreadHelper [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InvokeOnMainThread(Action action) { - Program.DispatcherQueue!.Invoke(action); + if (Program.DispatcherQueue!.HasThreadAccess) + { + action(); + } + else + { + Program.DispatcherQueue.Invoke(action); + } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/ImmutableIntrinsics.cs b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/IntrinsicImmutables.cs similarity index 97% rename from src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/ImmutableIntrinsics.cs rename to src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/IntrinsicImmutables.cs index 9c5b93b2..c5f872d9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/ImmutableIntrinsics.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Intrinsic/IntrinsicImmutables.cs @@ -9,7 +9,7 @@ namespace Snap.Hutao.Model.Intrinsic; /// /// 不可变的原生枚举 /// -public static class ImmutableIntrinsics +public static class IntrinsicImmutables { /// /// 所属地区 diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest index 4c8c4dc7..287f36e0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest @@ -12,7 +12,7 @@ + Version="1.3.10.0" /> 胡桃 diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Font/CascadiaMono.ttf b/src/Snap.Hutao/Snap.Hutao/Resource/Font/CascadiaMono.ttf new file mode 100644 index 00000000..d15637ef Binary files /dev/null and b/src/Snap.Hutao/Snap.Hutao/Resource/Font/CascadiaMono.ttf differ diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Font/MiSans-Regular.ttf b/src/Snap.Hutao/Snap.Hutao/Resource/Font/MiSans-Regular.ttf new file mode 100644 index 00000000..9cbfff50 Binary files /dev/null and b/src/Snap.Hutao/Snap.Hutao/Resource/Font/MiSans-Regular.ttf differ diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Font/Segoe Fluent Icons.ttf b/src/Snap.Hutao/Snap.Hutao/Resource/Font/Segoe Fluent Icons.ttf deleted file mode 100644 index 8f05a4bb..00000000 Binary files a/src/Snap.Hutao/Snap.Hutao/Resource/Font/Segoe Fluent Icons.ttf and /dev/null differ diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs index f492e7b1..08653279 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs @@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.EntityFrameworkCore; using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Diagnostics; +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.Logging; using Snap.Hutao.Extension; using Snap.Hutao.Model.Entity.Database; @@ -114,7 +115,17 @@ internal class AchievementService : IAchievementService List results = new(); foreach (MetadataAchievement meta in metadata) { - EntityAchievement entity = entities.SingleOrDefault(e => e.Id == meta.Id) ?? EntityAchievement.Create(archiveId, meta.Id); + EntityAchievement? entity; + try + { + entity = entities.SingleOrDefault(e => e.Id == meta.Id); + } + catch (InvalidOperationException ex) + { + throw new UserdataCorruptedException("单个成就存档内发现多个相同的成就 Id", ex); + } + + entity ??= EntityAchievement.Create(archiveId, meta.Id); results.Add(new(meta, entity)); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs index 05a8e176..a74cbd80 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs @@ -22,7 +22,6 @@ namespace Snap.Hutao.Service.AvatarInfo; [Injection(InjectAs.Scoped, typeof(IAvatarInfoService))] internal class AvatarInfoService : IAvatarInfoService { - private readonly AppDbContext appDbContext; private readonly ISummaryFactory summaryFactory; private readonly IMetadataService metadataService; private readonly ILogger logger; @@ -42,7 +41,6 @@ internal class AvatarInfoService : IAvatarInfoService ISummaryFactory summaryFactory, ILogger logger) { - this.appDbContext = appDbContext; this.metadataService = metadataService; this.summaryFactory = summaryFactory; this.logger = logger; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs index 540ae603..56de2214 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs @@ -43,7 +43,7 @@ internal class SummaryAvatarFactory ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull()); MetadataAvatar avatar = metadataContext.IdAvatarMap[avatarInfo.AvatarId]; - return new() + PropertyAvatar propertyAvatar = new() { Id = avatar.Id, Name = avatar.Name, @@ -52,8 +52,6 @@ internal class SummaryAvatarFactory NameCard = AvatarNameCardPicConverter.AvatarToUri(avatar), Quality = avatar.Quality, Element = ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore), - Level = $"Lv.{avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value}", - LevelNumber = int.Parse(avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value ?? string.Empty), FetterLevel = avatarInfo.FetterInfo.ExpLevel, Weapon = reliquaryAndWeapon.Weapon, Reliquaries = reliquaryAndWeapon.Reliquaries, @@ -62,7 +60,11 @@ internal class SummaryAvatarFactory Properties = SummaryHelper.CreateAvatarProperties(avatarInfo.FightPropMap), Score = reliquaryAndWeapon.Reliquaries.Sum(r => r.Score).ToString("F2"), CritScore = $"{SummaryHelper.ScoreCrit(avatarInfo.FightPropMap):F2}", + LevelNumber = int.Parse(avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].Value ?? string.Empty), }; + + propertyAvatar.Level = $"Lv.{propertyAvatar.LevelNumber}"; + return propertyAvatar; } private ReliquaryAndWeapon ProcessEquip(List equipments) @@ -85,7 +87,7 @@ internal class SummaryAvatarFactory } } - return new(reliquaryList, weapon!); + return new(reliquaryList, weapon); } private PropertyWeapon CreateWeapon(Equip equip) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs index 639707cb..d2efc73e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs @@ -47,6 +47,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient { + // Database items have been deleted by cascade deleting. entries?.RemoveWhere(n => n.UserId == message.RemovedUserId); }); } @@ -151,6 +152,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient().DailyNotes.RemoveAndSave(entry); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs index 64fdc997..f83897b3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.Messaging; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Diagnostics; @@ -296,6 +297,7 @@ internal class GachaLogService : IGachaLogService break; } + token.ThrowIfCancellationRequested(); SaveGachaItems(itemsToAdd, isLazy, archive, configration.EndId); await RandomDelayAsync(token).ConfigureAwait(false); } @@ -327,14 +329,21 @@ internal class GachaLogService : IGachaLogService if (archive != null) { - // TODO: replace with MaxBy - // https://github.com/dotnet/efcore/issues/25566 - // .MaxBy(i => i.Id); - item = appDbContext.GachaItems - .Where(i => i.ArchiveId == archive.InnerId) - .Where(i => i.QueryType == configType) - .OrderByDescending(i => i.Id) - .FirstOrDefault(); + try + { + // TODO: replace with MaxBy + // https://github.com/dotnet/efcore/issues/25566 + // .MaxBy(i => i.Id); + item = appDbContext.GachaItems + .Where(i => i.ArchiveId == archive.InnerId) + .Where(i => i.QueryType == configType) + .OrderByDescending(i => i.Id) + .FirstOrDefault(); + } + catch (SqliteException ex) + { + throw new Core.ExceptionService.UserdataCorruptedException("无法获取祈愿记录 End Id", ex); + } } return item?.Id ?? 0L; @@ -369,11 +378,12 @@ internal class GachaLogService : IGachaLogService private INameQuality GetNameQualityByItemId(int id) { - return id.Place() switch + int place = id.Place(); + return place switch { 8 => idAvatarMap![id], 5 => idWeaponMap![id], - _ => throw Must.NeverHappen(), + _ => throw Must.NeverHappen($"Id places: {place}"), }; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs index 1b402513..c86e24d0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs @@ -143,6 +143,11 @@ internal class GameService : IGameService, IDisposable string gamePath = GetGamePathSkipLocator(); string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFile); + if (!File.Exists(configPath)) + { + return new(null, null, configPath); + } + using (FileStream stream = File.OpenRead(configPath)) { List elements = IniSerializer.Deserialize(stream).ToList(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/MultiChannel.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/MultiChannel.cs index b50a2c3f..85247165 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/MultiChannel.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/MultiChannel.cs @@ -18,14 +18,21 @@ public struct MultiChannel /// public string SubChannel; + /// + /// 配置文件路径 当不为 null 时则存在文件读写问题 + /// + public string? ConfigFilePath; + /// /// 构造一个新的多通道 /// /// 通道 /// 子通道 - public MultiChannel(string? channel, string? subChannel) + /// 配置文件路径 + public MultiChannel(string? channel, string? subChannel, string? configFilePath = null) { Channel = channel ?? string.Empty; SubChannel = subChannel ?? string.Empty; + ConfigFilePath = configFilePath; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs index 83b38deb..507d3d4f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Unlocker/GameFpsUnlocker.cs @@ -6,6 +6,7 @@ using Snap.Hutao.Core.Diagnostics; using Snap.Hutao.Win32; using System.Diagnostics; using System.Runtime.InteropServices; +using Windows.Win32.Foundation; using Windows.Win32.System.Diagnostics.ToolHelp; using static Windows.Win32.PInvoke; @@ -75,21 +76,24 @@ internal class GameFpsUnlocker : IGameFpsUnlocker private static unsafe MODULEENTRY32 FindModule(int processId, string moduleName) { - using (SafeFileHandle snapshot = CreateToolhelp32Snapshot_SafeHandle(CREATE_TOOLHELP_SNAPSHOT_FLAGS.TH32CS_SNAPMODULE, (uint)processId)) + HANDLE snapshot = CreateToolhelp32Snapshot(CREATE_TOOLHELP_SNAPSHOT_FLAGS.TH32CS_SNAPMODULE, (uint)processId); + using (snapshot.AutoClose()) { Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError()); MODULEENTRY32 entry = StructMarshal.MODULEENTRY32(); bool found = false; - // First module must be exe. Ignoring it. - for (Module32First(snapshot, ref entry); Module32Next(snapshot, ref entry);) + bool loop = Module32First(snapshot, &entry); + while (loop) { if (entry.th32ProcessID == processId && entry.szModule.AsString() == moduleName) { found = true; break; } + + loop = Module32Next(snapshot, &entry); } return found ? entry : default; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs index 864cfecb..52a2cf63 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/NavigationService.cs @@ -181,7 +181,7 @@ internal class NavigationService : INavigationService /// public void GoBack() { - Program.DispatcherQueue!.TryEnqueue(() => + ThreadHelper.InvokeOnMainThread(() => { bool canGoBack = Frame?.CanGoBack ?? false; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index 731b23f4..759094c6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -124,7 +124,14 @@ internal class UserService : IUserService } userCollection = users.ToObservableCollection(); - Current = users.SingleOrDefault(user => user.IsSelected); + try + { + Current = users.SingleOrDefault(user => user.IsSelected); + } + catch (InvalidOperationException ex) + { + throw new Core.ExceptionService.UserdataCorruptedException("无法设置当前用户", ex); + } } return userCollection; @@ -157,8 +164,16 @@ internal class UserService : IUserService { if (userCollection != null) { - // TODO: optimize match speed. - return userCollection.SelectMany(u => u.UserGameRoles).SingleOrDefault(r => r.GameUid == uid); + try + { + // TODO: optimize match speed. + return userCollection.SelectMany(u => u.UserGameRoles).SingleOrDefault(r => r.GameUid == uid); + } + catch (InvalidOperationException) + { + // Sequence contains more than one matching element + // TODO: return a specialize UserGameRole to indicate error + } } return null; diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 7183b8c8..abe0a59b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -37,9 +37,12 @@ + + + @@ -122,7 +125,8 @@ - + + @@ -154,8 +158,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -171,14 +175,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -415,4 +419,9 @@ MSBuild:Compile + + + MSBuild:Compile + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml index 3bad90a3..67a1e04b 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/StatisticsCard.xaml @@ -64,25 +64,18 @@ - + - @@ -94,7 +87,7 @@ - + @@ -108,19 +101,23 @@ HorizontalContentAlignment="Stretch" Background="Transparent" BorderBrush="{x:Null}" + BorderThickness="0" + CornerRadius="4,4,0,0" IsExpanded="True"> + + 0,0,0,1 + - + - + @@ -165,14 +157,13 @@ Width="40" Height="40" Margin="4" - Background="{StaticResource CardBackgroundFillColorDefaultBrush}" + Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" Foreground="{StaticResource OrangeBrush}" IsIndeterminate="False" Maximum="{Binding GuarenteeOrangeThreshold}" Value="{Binding LastOrangePull}"/> - + @@ -204,14 +189,13 @@ Width="40" Height="40" Margin="4" - Background="{StaticResource CardBackgroundFillColorDefaultBrush}" + Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" Foreground="{StaticResource PurpleBrush}" IsIndeterminate="False" Maximum="{Binding GuarenteePurpleThreshold}" Value="{Binding LastPurplePull}"/> @@ -260,7 +240,7 @@ Text="五星"/> @@ -272,7 +252,7 @@ Text="四星"/> @@ -284,7 +264,7 @@ Text="三星"/> @@ -294,7 +274,6 @@ @@ -302,19 +281,16 @@ diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AdoptCalculatorDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AdoptCalculatorDialog.xaml index 4b8a488d..63c5b3de 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AdoptCalculatorDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AdoptCalculatorDialog.xaml @@ -4,17 +4,18 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - Title="米游社养成计算" Closed="OnContentDialogClosed" - DefaultButton="Primary" - PrimaryButtonText="完成" Style="{StaticResource DefaultContentDialogStyle}" mc:Ignorable="d"> - + + 0 + 0 + + Height="500" + AllowFocusOnInteraction="False"/> diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CommunityGameRecordDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CommunityGameRecordDialog.xaml index 89b35a8d..58bab248 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CommunityGameRecordDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CommunityGameRecordDialog.xaml @@ -8,15 +8,17 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Closed="OnContentDialogClosed" - DefaultButton="Primary" - PrimaryButtonText="完成" Style="{StaticResource DefaultContentDialogStyle}" mc:Ignorable="d"> - + + 0 + 0 + + Height="580" + AllowFocusOnInteraction="True"/> diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml index 457c3760..1c079c31 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml @@ -4,17 +4,19 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - Title="米游社实时便笺" Closed="OnContentDialogClosed" - DefaultButton="Primary" - PrimaryButtonText="完成" Style="{StaticResource DefaultContentDialogStyle}" mc:Ignorable="d"> - + + 0 + 0 + + Height="448" + AllowFocusOnInteraction="False" + DefaultBackgroundColor="Transparent"/> - + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml.cs index f87d66a1..f58f4f1a 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/DailyNoteVerificationDialog.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) DGP Studio. All rights reserved. +// Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. using Microsoft.Extensions.DependencyInjection; @@ -11,20 +11,19 @@ using Snap.Hutao.Web.Bridge; namespace Snap.Hutao.View.Dialog; /// -/// ʵʱ�����֤�Ի��� +/// 实时便笺验证对话框 /// public sealed partial class DailyNoteVerificationDialog : ContentDialog { private readonly IServiceScope scope; private readonly UserAndUid userAndUid; - [SuppressMessage("", "IDE0052")] private DailyNoteJsInterface? dailyNoteJsInterface; /// - /// ����һ���µ�ʵʱ�����֤�Ի��� + /// 构造一个新的实时便笺验证对话框 /// - /// �û����ɫ + /// 用户与角色 public DailyNoteVerificationDialog(UserAndUid userAndUid) { InitializeComponent(); @@ -46,14 +45,21 @@ public sealed partial class DailyNoteVerificationDialog : ContentDialog Model.Entity.User user = userAndUid.User; coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent(); dailyNoteJsInterface = new(coreWebView2, scope.ServiceProvider); + dailyNoteJsInterface.ClosePageRequested += OnClosePageRequested; string query = $"?role_id={userAndUid.Uid.Value}&server={userAndUid.Uid.Region}"; coreWebView2.Navigate($"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/{query}"); } + private void OnClosePageRequested() + { + ThreadHelper.InvokeOnMainThread(Hide); + } + private void OnContentDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args) { + dailyNoteJsInterface.ClosePageRequested -= OnClosePageRequested; dailyNoteJsInterface = null; scope.Dispose(); } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Extension/CoreWebView2EnvironmentExtension.cs b/src/Snap.Hutao/Snap.Hutao/View/Extension/CoreWebView2EnvironmentExtension.cs new file mode 100644 index 00000000..da3d522e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Extension/CoreWebView2EnvironmentExtension.cs @@ -0,0 +1,31 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Web.WebView2.Core; +using System.Diagnostics; + +namespace Snap.Hutao.View.Extension; + +/// +/// 扩展 +/// +internal static class CoreWebView2EnvironmentExtension +{ + /// + /// 退出 + /// + /// 环境 + public static void Exit(this CoreWebView2Environment environment) + { + IReadOnlyList processInfos = environment.GetProcessInfos(); + + foreach (CoreWebView2ProcessInfo processInfo in processInfos) + { + Process p = Process.GetProcessById(processInfo.ProcessId); + if (p.ProcessName == "msedgewebview2.exe") + { + p.Kill(); + } + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml index 3671b0c7..d58e02e2 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml @@ -160,6 +160,9 @@ Margin="16,0,0,16" ItemsPanel="{StaticResource ItemsStackPanelTemplate}" ItemsSource="{Binding Achievements}"> + + + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + base.OnNavigatedFrom(e); + //WebView.CoreWebView2.Environment.Exit(); + } + private static string? ReplaceForeground(string? rawContent, ElementTheme theme) { if (string.IsNullOrWhiteSpace(rawContent)) diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml index 3ac6b3ea..d85347b9 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AnnouncementPage.xaml @@ -149,7 +149,7 @@ - + + Text="{Binding SelectedAvatar.FetterLevel}" + TextAlignment="Center" + TextTrimming="None" + TextWrapping="NoWrap"/> diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml index 5eedd865..e14257c0 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml @@ -481,7 +481,8 @@ + Icon="{shcm:FontIcon Glyph=}" + Style="{StaticResource DefaultSettingStyle}">