diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs
new file mode 100644
index 00000000..13dfab76
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncBarrier.cs
@@ -0,0 +1,65 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Core.Threading;
+
+///
+/// An asynchronous barrier that blocks the signaler until all other participants have signaled.
+///
+internal class AsyncBarrier
+{
+ ///
+ /// The number of participants being synchronized.
+ ///
+ private readonly int participantCount;
+
+ ///
+ /// The set of participants who have reached the barrier, with their awaiters that can resume those participants.
+ ///
+ private readonly Stack waiters;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The number of participants.
+ public AsyncBarrier(int participants)
+ {
+ Requires.Range(participants > 0, nameof(participants));
+ participantCount = participants;
+
+ // Allocate the stack so no resizing is necessary.
+ // We don't need space for the last participant, since we never have to store it.
+ waiters = new Stack(participants - 1);
+ }
+
+ ///
+ /// Signals that a participant is ready, and returns a Task
+ /// that completes when all other participants have also signaled ready.
+ ///
+ /// A Task, which will complete (or may already be completed) when the last participant calls this method.
+ public Task SignalAndWaitAsync()
+ {
+ lock (waiters)
+ {
+ if (waiters.Count + 1 == participantCount)
+ {
+ // This is the last one we were waiting for.
+ // Unleash everyone that preceded this one.
+ while (waiters.Count > 0)
+ {
+ _ = Task.Factory.StartNew(state => ((TaskCompletionSource)state!).SetResult(), waiters.Pop(), default, TaskCreationOptions.None, TaskScheduler.Default);
+ }
+
+ // And allow this one to continue immediately.
+ return Task.CompletedTask;
+ }
+ else
+ {
+ // We need more folks. So suspend this caller.
+ TaskCompletionSource tcs = new();
+ waiters.Push(tcs);
+ return tcs.Task;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/NumberExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/NumberExtension.cs
index 268a24ba..ee452223 100644
--- a/src/Snap.Hutao/Snap.Hutao/Extension/NumberExtension.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Extension/NumberExtension.cs
@@ -17,7 +17,7 @@ internal static class NumberExtension
/// 给定的整数
/// 位数
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static uint Place(in this uint x)
+ public static uint StringLength(in this uint x)
{
// Benchmarked and compared as a most optimized solution
return (uint)(MathF.Log10(x) + 1);
diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs
index eec2f361..18db77a0 100644
--- a/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs
@@ -51,4 +51,27 @@ internal static class SpanExtension
right = default;
return false;
}
+
+ ///
+ /// 求平均值
+ ///
+ /// 跨度
+ /// 平均值
+ public static byte Average(this in ReadOnlySpan span)
+ {
+ if (span.Length == 0)
+ {
+ return 0;
+ }
+
+ int sum = 0;
+ int count = 0;
+ foreach (ref readonly byte b in span)
+ {
+ sum += b;
+ count++;
+ }
+
+ return unchecked((byte)(sum / count));
+ }
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFItem.cs
index 5538f97e..1f7df4fd 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFItem.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGFItem.cs
@@ -40,7 +40,7 @@ internal sealed class UIGFItem : GachaLogItem, IMappingFrom SH.ModelInterchangeUIGFItemTypeAvatar,
5U => SH.ModelInterchangeUIGFItemTypeWeapon,
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs
index 3cbcc597..dac8a7a7 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs
@@ -54,10 +54,10 @@ internal sealed class GachaEvent
///
/// 五星列表
///
- public List UpOrangeList { get; set; } = default!;
+ public HashSet UpOrangeList { get; set; } = default!;
///
/// 四星列表
///
- public List UpPurpleList { get; set; } = default!;
+ public HashSet UpPurpleList { get; set; } = default!;
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_210.png b/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_210.png
index ce3e342c..636ce0a2 100644
Binary files a/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_210.png and b/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_210.png differ
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_210_256.png b/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_210_256.png
deleted file mode 100644
index 636ce0a2..00000000
Binary files a/src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_ItemIcon_210_256.png and /dev/null differ
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs
index 8717142c..22239f7e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs
@@ -12,7 +12,9 @@ internal interface ICultivationDbService
ValueTask DeleteCultivateEntryByIdAsync(Guid entryId);
ValueTask DeleteCultivateItemRangeByEntryIdAsync(Guid entryId);
+
ValueTask DeleteCultivateProjectByIdAsync(Guid projectId);
+
ValueTask GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId);
ValueTask> GetCultivateEntryListByProjectIdAsync(Guid projectId);
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteDbService.cs
new file mode 100644
index 00000000..68fa3d22
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteDbService.cs
@@ -0,0 +1,80 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using CommunityToolkit.Mvvm.Messaging;
+using Microsoft.EntityFrameworkCore;
+using Snap.Hutao.Core.Database;
+using Snap.Hutao.Core.DependencyInjection.Abstraction;
+using Snap.Hutao.Message;
+using Snap.Hutao.Model.Entity;
+using Snap.Hutao.Model.Entity.Database;
+using Snap.Hutao.Service.Notification;
+using Snap.Hutao.Service.User;
+using Snap.Hutao.ViewModel.User;
+using Snap.Hutao.Web.Hoyolab;
+using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
+using System.Collections.ObjectModel;
+using WebDailyNote = Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote;
+
+namespace Snap.Hutao.Service.DailyNote;
+
+[ConstructorGenerated]
+[Injection(InjectAs.Singleton, typeof(IDailyNoteDbService))]
+internal sealed partial class DailyNoteDbService : IDailyNoteDbService
+{
+ private readonly IServiceProvider serviceProvider;
+
+ public bool ContainsUid(string uid)
+ {
+ using (IServiceScope scope = serviceProvider.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ return appDbContext.DailyNotes.AsNoTracking().Any(n => n.Uid == uid);
+ }
+ }
+
+ public async ValueTask AddDailyNoteEntryAsync(DailyNoteEntry entry)
+ {
+ using (IServiceScope scope = serviceProvider.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ await appDbContext.DailyNotes.AddAndSaveAsync(entry).ConfigureAwait(false);
+ }
+ }
+
+ public async ValueTask DeleteDailyNoteEntryByIdAsync(Guid entryId)
+ {
+ using (IServiceScope scope = serviceProvider.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ await appDbContext.DailyNotes.ExecuteDeleteWhereAsync(d => d.InnerId == entryId).ConfigureAwait(false);
+ }
+ }
+
+ public async ValueTask UpdateDailyNoteEntryAsync(DailyNoteEntry entry)
+ {
+ using (IServiceScope scope = serviceProvider.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ await appDbContext.DailyNotes.UpdateAndSaveAsync(entry).ConfigureAwait(false);
+ }
+ }
+
+ public List GetDailyNoteEntryList()
+ {
+ using (IServiceScope scope = serviceProvider.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ return appDbContext.DailyNotes.AsNoTracking().ToList();
+ }
+ }
+
+ public List GetDailyNoteEntryIncludeUserList()
+ {
+ using (IServiceScope scope = serviceProvider.CreateScope())
+ {
+ AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ return appDbContext.DailyNotes.AsNoTracking().Include(n => n.User).ToList();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotificationOperation.cs
similarity index 90%
rename from src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs
rename to src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotificationOperation.cs
index 4f8ec94b..d1890b35 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotifier.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteNotificationOperation.cs
@@ -18,7 +18,7 @@ namespace Snap.Hutao.Service.DailyNote;
/// 实时便笺通知器
///
[HighQuality]
-internal sealed class DailyNoteNotifier
+internal sealed class DailyNoteNotificationOperation
{
private const string ToastHeaderIdArgument = "DAILYNOTE";
private const string ToastAttributionUnknown = "Unknown";
@@ -31,7 +31,7 @@ internal sealed class DailyNoteNotifier
///
/// 服务提供器
/// 实时便笺入口
- public DailyNoteNotifier(IServiceProvider serviceProvider, DailyNoteEntry entry)
+ public DailyNoteNotificationOperation(IServiceProvider serviceProvider, DailyNoteEntry entry)
{
taskContext = serviceProvider.GetRequiredService();
this.serviceProvider = serviceProvider;
@@ -42,7 +42,7 @@ internal sealed class DailyNoteNotifier
/// 异步通知
///
/// 任务
- public async ValueTask NotifyAsync()
+ public async ValueTask SendAsync()
{
if (entry.DailyNote == null)
{
@@ -131,6 +131,9 @@ internal sealed class DailyNoteNotifier
private static void CheckNotifySuppressed(DailyNoteEntry entry, List notifyInfos)
{
+ // https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/send-local-toast?tabs=uwp#adding-images
+ // Image limitation.
+
// NotifySuppressed judge
if (entry.DailyNote!.CurrentResin >= entry.ResinNotifyThreshold)
{
@@ -138,7 +141,7 @@ internal sealed class DailyNoteNotifier
{
notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierResin,
- "ms-appx:///Resource/Icon/UI_ItemIcon_210_256.png",
+ Web.Hoyolab.OssImages.UIItemIcon210,
$"{entry.DailyNote.CurrentResin}",
string.Format(SH.ServiceDailyNoteNotifierResinCurrent, entry.DailyNote.CurrentResin)));
entry.ResinNotifySuppressed = true;
@@ -155,7 +158,7 @@ internal sealed class DailyNoteNotifier
{
notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierHomeCoin,
- "ms-appx:///Resource/Icon/UI_ItemIcon_204.png",
+ Web.Hoyolab.OssImages.UIItemIcon204,
$"{entry.DailyNote.CurrentHomeCoin}",
string.Format(SH.ServiceDailyNoteNotifierHomeCoinCurrent, entry.DailyNote.CurrentHomeCoin)));
entry.HomeCoinNotifySuppressed = true;
@@ -172,7 +175,7 @@ internal sealed class DailyNoteNotifier
{
notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierDailyTask,
- "ms-appx:///Resource/Icon/UI_MarkQuest_Events_Proce.png",
+ Web.Hoyolab.OssImages.UIMarkQuestEventsProce,
SH.ServiceDailyNoteNotifierDailyTaskHint,
entry.DailyNote.ExtraTaskRewardDescription));
entry.DailyTaskNotifySuppressed = true;
@@ -189,7 +192,7 @@ internal sealed class DailyNoteNotifier
{
notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierTransformer,
- "ms-appx:///Resource/Icon/UI_ItemIcon_220021.png",
+ Web.Hoyolab.OssImages.UIItemIcon220021,
SH.ServiceDailyNoteNotifierTransformerAdaptiveHint,
SH.ServiceDailyNoteNotifierTransformerHint));
entry.TransformerNotifySuppressed = true;
@@ -206,7 +209,7 @@ internal sealed class DailyNoteNotifier
{
notifyInfos.Add(new(
SH.ServiceDailyNoteNotifierExpedition,
- Web.HutaoEndpoints.UIAvatarIconSideNone.ToString(), // TODO: embed this
+ Web.Hoyolab.OssImages.UIIconInteeExplore1,
SH.ServiceDailyNoteNotifierExpeditionAdaptiveHint,
SH.ServiceDailyNoteNotifierExpeditionHint));
entry.ExpeditionNotifySuppressed = true;
@@ -220,17 +223,8 @@ internal sealed class DailyNoteNotifier
private bool ShouldSuppressPopup(DailyNoteOptions options)
{
- bool isGameRunning = serviceProvider.GetRequiredService().IsGameRunning();
-
- if (options.IsSilentWhenPlayingGame && isGameRunning)
- {
- // Prevent notify when we are in game && silent mode.
- return true;
- }
- else
- {
- return false;
- }
+ // Prevent notify when we are in game && silent mode.
+ return options.IsSilentWhenPlayingGame && serviceProvider.GetRequiredService().IsGameRunning();
}
private readonly struct NotifyInfo
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteOptions.cs
index 549bd83e..0258e1c1 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteOptions.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteOptions.cs
@@ -15,6 +15,8 @@ namespace Snap.Hutao.Service.DailyNote;
[Injection(InjectAs.Singleton)]
internal sealed class DailyNoteOptions : DbStoreOptions
{
+ private const int OneMinute = 60;
+
private readonly IServiceProvider serviceProvider;
private readonly IScheduleTaskInterop scheduleTaskInterop;
@@ -38,11 +40,11 @@ internal sealed class DailyNoteOptions : DbStoreOptions
///
public List> RefreshTimes { get; } = new()
{
- new(SH.ViewModelDailyNoteRefreshTime4, 240),
- new(SH.ViewModelDailyNoteRefreshTime8, 480),
- new(SH.ViewModelDailyNoteRefreshTime30, 1800),
- new(SH.ViewModelDailyNoteRefreshTime40, 2400),
- new(SH.ViewModelDailyNoteRefreshTime60, 3600),
+ new(SH.ViewModelDailyNoteRefreshTime4, OneMinute * 4),
+ new(SH.ViewModelDailyNoteRefreshTime8, OneMinute * 8),
+ new(SH.ViewModelDailyNoteRefreshTime30, OneMinute * 30),
+ new(SH.ViewModelDailyNoteRefreshTime40, OneMinute * 40),
+ new(SH.ViewModelDailyNoteRefreshTime60, OneMinute * 60),
};
///
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs
index 6ce0995d..5309043e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/DailyNoteService.cs
@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
+using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Message;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
@@ -26,6 +27,7 @@ namespace Snap.Hutao.Service.DailyNote;
internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient
{
private readonly IServiceProvider serviceProvider;
+ private readonly IDailyNoteDbService dailyNoteDbService;
private readonly IUserService userService;
private readonly ITaskContext taskContext;
@@ -42,30 +44,27 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient();
+ DailyNoteEntry newEntry = DailyNoteEntry.From(role);
- if (!appDbContext.DailyNotes.Any(n => n.Uid == roleUid))
+ Web.Response.Response dailyNoteResponse = await serviceProvider
+ .GetRequiredService>()
+ .Create(PlayerUid.IsOversea(roleUid))
+ .GetDailyNoteAsync(role)
+ .ConfigureAwait(false);
+
+ if (dailyNoteResponse.IsOk())
{
- DailyNoteEntry newEntry = DailyNoteEntry.From(role);
-
- Web.Response.Response dailyNoteResponse = await scope.ServiceProvider
- .PickRequiredService(PlayerUid.IsOversea(roleUid))
- .GetDailyNoteAsync(role)
- .ConfigureAwait(false);
-
- if (dailyNoteResponse.IsOk())
- {
- newEntry.UpdateDailyNote(dailyNoteResponse.Data);
- }
-
- newEntry.UserGameRole = userService.GetUserGameRoleByUid(roleUid);
- await appDbContext.DailyNotes.AddAndSaveAsync(newEntry).ConfigureAwait(false);
-
- await taskContext.SwitchToMainThreadAsync();
- entries?.Add(newEntry);
+ newEntry.UpdateDailyNote(dailyNoteResponse.Data);
}
+
+ newEntry.UserGameRole = userService.GetUserGameRoleByUid(roleUid);
+ await dailyNoteDbService.AddDailyNoteEntryAsync(newEntry).ConfigureAwait(false);
+
+ await taskContext.SwitchToMainThreadAsync();
+ entries?.Add(newEntry);
}
}
@@ -76,13 +75,9 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient();
- List entryList = appDbContext.DailyNotes.ToList();
- entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); });
- entries = new(entryList);
- }
+ List entryList = dailyNoteDbService.GetDailyNoteEntryList();
+ entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); });
+ entries = new(entryList);
}
return entries;
@@ -91,31 +86,26 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient
public async ValueTask RefreshDailyNotesAsync()
{
- using (IServiceScope scope = serviceProvider.CreateScope())
+ foreach (DailyNoteEntry entry in dailyNoteDbService.GetDailyNoteEntryIncludeUserList())
{
- AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService();
+ Web.Response.Response dailyNoteResponse = await serviceProvider
+ .GetRequiredService>()
+ .Create(PlayerUid.IsOversea(entry.Uid))
+ .GetDailyNoteAsync(new(entry.User, entry.Uid))
+ .ConfigureAwait(false);
- foreach (DailyNoteEntry entry in appDbContext.DailyNotes.Include(n => n.User))
+ if (dailyNoteResponse.IsOk())
{
- Web.Response.Response dailyNoteResponse = await scope.ServiceProvider
- .PickRequiredService(PlayerUid.IsOversea(entry.Uid))
- .GetDailyNoteAsync(new(entry.User, entry.Uid))
- .ConfigureAwait(false);
+ WebDailyNote dailyNote = dailyNoteResponse.Data!;
- if (dailyNoteResponse.IsOk())
- {
- WebDailyNote dailyNote = dailyNoteResponse.Data!;
+ // cache
+ await taskContext.SwitchToMainThreadAsync();
+ entries?.SingleOrDefault(e => e.UserId == entry.UserId && e.Uid == entry.Uid)?.UpdateDailyNote(dailyNote);
- // database
- entry.UpdateDailyNote(dailyNote);
-
- // cache
- await taskContext.SwitchToMainThreadAsync();
- entries?.SingleOrDefault(e => e.UserId == entry.UserId && e.Uid == entry.Uid)?.UpdateDailyNote(dailyNote);
-
- await new DailyNoteNotifier(serviceProvider, entry).NotifyAsync().ConfigureAwait(false);
- await appDbContext.DailyNotes.UpdateAndSaveAsync(entry).ConfigureAwait(false);
- }
+ // database
+ await new DailyNoteNotificationOperation(serviceProvider, entry).SendAsync().ConfigureAwait(false);
+ entry.DailyNote = dailyNote;
+ await dailyNoteDbService.UpdateDailyNoteEntryAsync(entry).ConfigureAwait(false);
}
}
}
@@ -124,13 +114,10 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient();
- await appDbContext.DailyNotes.ExecuteDeleteWhereAsync(d => d.InnerId == entry.InnerId).ConfigureAwait(false);
- }
+ await dailyNoteDbService.DeleteDailyNoteEntryByIdAsync(entry.InnerId).ConfigureAwait(false);
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteDbService.cs
new file mode 100644
index 00000000..1878f37d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/DailyNote/IDailyNoteDbService.cs
@@ -0,0 +1,21 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Entity;
+
+namespace Snap.Hutao.Service.DailyNote;
+
+internal interface IDailyNoteDbService
+{
+ ValueTask AddDailyNoteEntryAsync(DailyNoteEntry entry);
+
+ bool ContainsUid(string uid);
+
+ ValueTask DeleteDailyNoteEntryByIdAsync(Guid entryId);
+
+ List GetDailyNoteEntryIncludeUserList();
+
+ List GetDailyNoteEntryList();
+
+ ValueTask UpdateDailyNoteEntryAsync(DailyNoteEntry entry);
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtension.cs
index 46628523..cc34c7ef 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtension.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsExtension.cs
@@ -14,29 +14,6 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
///
internal static class GachaStatisticsExtension
{
- ///
- /// 求平均值
- ///
- /// 跨度
- /// 平均值
- public static byte Average(this in Span span)
- {
- int sum = 0;
- int count = 0;
- foreach (ref readonly byte b in span)
- {
- sum += b;
- count++;
- }
-
- if (count == 0)
- {
- return 0;
- }
-
- return unchecked((byte)(sum / count));
- }
-
///
/// 完成添加
///
@@ -82,7 +59,7 @@ internal static class GachaStatisticsExtension
[SuppressMessage("", "IDE0057")]
private static Color GetColorByName(string name)
{
- Span codes = MD5.HashData(Encoding.UTF8.GetBytes(name));
+ ReadOnlySpan codes = MD5.HashData(Encoding.UTF8.GetBytes(name));
return Color.FromArgb(255, codes.Slice(0, 5).Average(), codes.Slice(5, 5).Average(), codes.Slice(10, 5).Average());
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs
index 4e82867f..c5e8d203 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/GachaStatisticsFactory.cs
@@ -22,11 +22,13 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
{
private readonly IServiceProvider serviceProvider;
private readonly IMetadataService metadataService;
+ private readonly ITaskContext taskContext;
private readonly AppOptions options;
///
public async Task CreateAsync(IOrderedQueryable items, GachaLogServiceContext context)
{
+ await taskContext.SwitchToBackgroundAsync();
List gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
List historyWishBuilders = gachaEvents.SelectList(g => new HistoryWishBuilder(g, context));
@@ -44,23 +46,18 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
serviceProvider,
SH.ServiceGachaLogFactoryPermanentWishName,
TypedWishSummaryBuilder.IsStandardWish,
- Web.Hutao.GachaLog.GachaDistributionType.Standard,
- 90,
- 10);
+ Web.Hutao.GachaLog.GachaDistributionType.Standard);
TypedWishSummaryBuilder avatarWishBuilder = new(
serviceProvider,
SH.ServiceGachaLogFactoryAvatarWishName,
TypedWishSummaryBuilder.IsAvatarEventWish,
- Web.Hutao.GachaLog.GachaDistributionType.AvatarEvent,
- 90,
- 10);
+ Web.Hutao.GachaLog.GachaDistributionType.AvatarEvent);
TypedWishSummaryBuilder weaponWishBuilder = new(
serviceProvider,
SH.ServiceGachaLogFactoryWeaponWishName,
TypedWishSummaryBuilder.IsWeaponEventWish,
Web.Hutao.GachaLog.GachaDistributionType.WeaponEvent,
- 80,
- 10);
+ 80);
Dictionary orangeAvatarCounter = new();
Dictionary purpleAvatarCounter = new();
@@ -77,8 +74,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
.Where(w => w.ConfigType == item.GachaType)
.SingleOrDefault(w => w.From <= item.Time && w.To >= item.Time);
- // It's an avatar
- switch (item.ItemId.Place())
+ switch (item.ItemId.StringLength())
{
case 8U:
{
@@ -135,7 +131,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
}
default:
- // ItemId place not correct.
+ // ItemId string length not correct.
ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceGachaStatisticsFactoryItemIdInvalid, item.ItemId), null!);
break;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactory.cs
index 422266c8..506e4eb2 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactory.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactory.cs
@@ -1,12 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Abstraction;
-using Snap.Hutao.Model.Metadata.Avatar;
-using Snap.Hutao.Model.Metadata.Weapon;
-using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog;
@@ -16,21 +14,19 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
internal sealed class HutaoStatisticsFactory
{
- private readonly Dictionary idAvatarMap;
- private readonly Dictionary idWeaponMap;
+ private readonly HutaoStatisticsFactoryMetadataContext context;
private readonly GachaEvent avatarEvent;
private readonly GachaEvent avatarEvent2;
private readonly GachaEvent weaponEvent;
- public HutaoStatisticsFactory(Dictionary idAvatarMap, Dictionary idWeaponMap, List gachaEvents)
+ public HutaoStatisticsFactory(in HutaoStatisticsFactoryMetadataContext context)
{
- this.idAvatarMap = idAvatarMap;
- this.idWeaponMap = idWeaponMap;
+ this.context = context;
DateTimeOffset now = DateTimeOffset.Now;
- avatarEvent = gachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish);
- avatarEvent2 = gachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish2);
- weaponEvent = gachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.WeaponEventWish);
+ avatarEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish);
+ avatarEvent2 = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.AvatarEventWish2);
+ weaponEvent = context.GachaEvents.Single(g => g.From < now && g.To > now && g.Type == GachaConfigType.WeaponEventWish);
}
public HutaoStatistics Create(GachaEventStatistics raw)
@@ -52,11 +48,11 @@ internal sealed class HutaoStatisticsFactory
foreach (ref readonly ItemCount item in CollectionsMarshal.AsSpan(items))
{
- IStatisticsItemSource source = item.Item.Place() switch
+ IStatisticsItemSource source = item.Item.StringLength() switch
{
- 8U => idAvatarMap[item.Item],
- 5U => idWeaponMap[item.Item],
- _ => throw Must.NeverHappen($"不支持的物品 Id:{item.Item}"),
+ 8U => context.IdAvatarMap[item.Item],
+ 5U => context.IdWeaponMap[item.Item],
+ _ => throw ThrowHelper.UserdataCorrupted(string.Format(SH.ServiceGachaStatisticsFactoryItemIdInvalid, item.Item), null!),
};
StatisticsItem statisticsItem = source.ToStatisticsItem(unchecked((int)item.Count));
@@ -73,6 +69,7 @@ internal sealed class HutaoStatisticsFactory
QualityType.QUALITY_BLUE => blueItems,
_ => throw Must.NeverHappen("意外的物品等级"),
};
+
list.Add(statisticsItem);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactoryMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactoryMetadataContext.cs
new file mode 100644
index 00000000..9076274d
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/HutaoStatisticsFactoryMetadataContext.cs
@@ -0,0 +1,23 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Metadata;
+using Snap.Hutao.Model.Metadata.Avatar;
+using Snap.Hutao.Model.Metadata.Weapon;
+using Snap.Hutao.Model.Primitive;
+
+namespace Snap.Hutao.Service.GachaLog.Factory;
+
+internal readonly struct HutaoStatisticsFactoryMetadataContext
+{
+ public readonly Dictionary IdAvatarMap;
+ public readonly Dictionary IdWeaponMap;
+ public readonly List GachaEvents;
+
+ public HutaoStatisticsFactoryMetadataContext(Dictionary idAvatarMap, Dictionary idWeaponMap, List gachaEvents)
+ {
+ IdAvatarMap = idAvatarMap;
+ IdWeaponMap = idWeaponMap;
+ GachaEvents = gachaEvents;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs
index 595b4652..051271bf 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/Factory/TypedWishSummaryBuilder.cs
@@ -68,8 +68,8 @@ internal sealed class TypedWishSummaryBuilder
string name,
Func typeEvaluator,
Web.Hutao.GachaLog.GachaDistributionType distributionType,
- int guaranteeOrangeThreshold,
- int guaranteePurpleThreshold)
+ int guaranteeOrangeThreshold = 90,
+ int guaranteePurpleThreshold = 10)
{
this.serviceProvider = serviceProvider;
this.name = name;
@@ -171,6 +171,7 @@ internal sealed class TypedWishSummaryBuilder
OrangeList = summaryItems,
};
+ // TODO: barrier all predictions.
new PullPrediction(serviceProvider, summary, distributionType).PredictAsync().SafeForget();
return summary;
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceContext.cs
index 03cc1665..02980689 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceContext.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/GachaLogServiceContext.cs
@@ -106,7 +106,7 @@ internal readonly struct GachaLogServiceContext
/// 名称星级
public INameQuality GetNameQualityByItemId(uint id)
{
- uint place = id.Place();
+ uint place = id.StringLength();
return place switch
{
8U => IdAvatarMap[id],
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/HutaoCloudService.cs b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/HutaoCloudService.cs
index 63fadd86..a71ae389 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/HutaoCloudService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/GachaLog/HutaoCloudService.cs
@@ -8,6 +8,7 @@ using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
+using Snap.Hutao.Service.GachaLog.Factory;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
@@ -99,17 +100,19 @@ internal sealed partial class HutaoCloudService : IHutaoCloudService
public async Task> GetCurrentEventStatisticsAsync(CancellationToken token = default)
{
IMetadataService metadataService = serviceProvider.GetRequiredService();
- if (await metadataService.InitializeAsync().ConfigureAwait(false))
- {
- Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
- Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
- List gachaEvents = await metadataService.GetGachaEventsAsync(token).ConfigureAwait(false);
- Response response = await homaGachaLogClient.GetGachaEventStatisticsAsync(token).ConfigureAwait(false);
- if (response.IsOk())
+ Response response = await homaGachaLogClient.GetGachaEventStatisticsAsync(token).ConfigureAwait(false);
+ if (response.IsOk())
+ {
+ if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
+ Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
+ Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
+ List gachaEvents = await metadataService.GetGachaEventsAsync(token).ConfigureAwait(false);
+ HutaoStatisticsFactoryMetadataContext context = new(idAvatarMap, idWeaponMap, gachaEvents);
+
GachaEventStatistics raw = response.Data;
- Factory.HutaoStatisticsFactory factory = new(idAvatarMap, idWeaponMap, gachaEvents);
+ HutaoStatisticsFactory factory = new(context);
HutaoStatistics statistics = factory.Create(raw);
return new(true, statistics);
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
index afeec2e6..c24a6729 100644
--- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
+++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj
@@ -99,7 +99,6 @@
-
@@ -230,7 +229,6 @@
-
diff --git a/src/Snap.Hutao/Snap.Hutao/View/Card/DailyNoteCard.xaml b/src/Snap.Hutao/Snap.Hutao/View/Card/DailyNoteCard.xaml
index 9fbbae56..87d3ade6 100644
--- a/src/Snap.Hutao/Snap.Hutao/View/Card/DailyNoteCard.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/View/Card/DailyNoteCard.xaml
@@ -48,7 +48,7 @@
-
+
-
+