Compare commits

..

1 Commits

Author SHA1 Message Date
qhy040404
37767261cd impl #1100 2024-04-26 11:21:09 +08:00
34 changed files with 444 additions and 358 deletions

View File

@@ -104,7 +104,6 @@ internal class ScopedPage : Page
// Dispose the scope
pageScope.Dispose();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
}
}
}

View File

@@ -13,7 +13,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("cultivate_entries")]
internal sealed class CultivateEntry : IDbMappingForeignKeyFrom<CultivateEntry, CultivateType, uint>, IAppDbEntity
internal sealed class CultivateEntry : IDbMappingForeignKeyFrom<CultivateEntry, CultivateType, uint>
{
/// <summary>
/// 内部Id

View File

@@ -3,7 +3,6 @@
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Abstraction;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -14,7 +13,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("cultivate_projects")]
internal sealed class CultivateProject : ISelectable, IMappingFrom<CultivateProject, string, string>, IAppDbEntity
internal sealed class CultivateProject : ISelectable, IMappingFrom<CultivateProject, string, string>
{
/// <summary>
/// 内部Id

View File

@@ -16,6 +16,9 @@ internal sealed partial class SettingEntry
public const string ElementTheme = "ElementTheme";
public const string BackgroundImageType = "BackgroundImageType";
public const string IsAutoUploadGachaLogEnabled = "IsAutoUploadGachaLogEnabled";
public const string IsAutoUploadSpiralAbyssRecordEnabled = "IsAutoUploadSpiralAbyssRecordEnabled";
public const string AnnouncementRegion = "AnnouncementRegion";
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";

View File

@@ -1361,18 +1361,6 @@
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
<value>是否永久删除用户数据</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText" xml:space="preserve">
<value>前往登录</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText" xml:space="preserve">
<value>继续上传</value>
</data>
<data name="ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">
<value>上传深渊数据</value>
</data>
<data name="ViewDialogUpdatePackageDownloadUpdatelogLinkContent" xml:space="preserve">
<value>查看更新日志</value>
</data>
@@ -1745,6 +1733,12 @@
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
<value>保存游戏路径失败</value>
</data>
<data name="ViewModelSpiralAbyssUploadRecordHomaNotLoginContent" xml:space="preserve">
<value>当前未登录胡桃账号,上传深渊数据无法获赠胡桃云时长</value>
</data>
<data name="ViewModelSpiralAbyssUploadRecordHomaNotLoginTitle" xml:space="preserve">
<value>上传深渊数据</value>
</data>
<data name="ViewModelUserAdded" xml:space="preserve">
<value>用户 [{0}] 添加成功</value>
</data>
@@ -2594,6 +2588,24 @@
<data name="ViewpageSettingHomeHeader" xml:space="preserve">
<value>主页</value>
</data>
<data name="ViewPageSettingHutaoCloudAutoUploadDescription" xml:space="preserve">
<value>登录胡桃通行证后自动上传至胡桃云</value>
</data>
<data name="ViewPageSettingHutaoCloudAutoUploadHeader" xml:space="preserve">
<value>自动上传</value>
</data>
<data name="ViewPageSettingHutaoCloudGachaLogAutoUploadDescription" xml:space="preserve">
<value>刷新祈愿记录后自动上传至胡桃云,需要有效的胡桃云服务</value>
</data>
<data name="ViewPageSettingHutaoCloudGachaLogAutoUploadHeader" xml:space="preserve">
<value>自动上传祈愿记录</value>
</data>
<data name="ViewPageSettingHutaoCloudSpiralAbyssAutoUploadDescription" xml:space="preserve">
<value>刷新深境螺旋数据后自动上传至胡桃云</value>
</data>
<data name="ViewPageSettingHutaoCloudSpiralAbyssAutoUploadHeader" xml:space="preserve">
<value>自动上传深境螺旋数据</value>
</data>
<data name="ViewPageSettingHutaoPassportDangerZoneDescription" xml:space="preserve">
<value>三思而后行</value>
</data>

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Abstraction;
namespace Snap.Hutao.Service.Abstraction;
@@ -11,25 +12,13 @@ internal static class AppDbServiceAppDbEntityExtension
public static int DeleteByInnerId<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
where TEntity : class, IAppDbEntity
{
return service.DeleteByInnerId(entity.InnerId);
}
public static int DeleteByInnerId<TEntity>(this IAppDbService<TEntity> service, Guid innerId)
where TEntity : class, IAppDbEntity
{
return service.Delete(e => e.InnerId == innerId);
return service.Execute(dbset => dbset.ExecuteDeleteWhere(e => e.InnerId == entity.InnerId));
}
public static ValueTask<int> DeleteByInnerIdAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
where TEntity : class, IAppDbEntity
{
return service.DeleteByInnerIdAsync(entity.InnerId, token);
}
public static ValueTask<int> DeleteByInnerIdAsync<TEntity>(this IAppDbService<TEntity> service, Guid innerId, CancellationToken token = default)
where TEntity : class, IAppDbEntity
{
return service.DeleteAsync(e => e.InnerId == innerId, token);
return service.ExecuteAsync((dbset, token) => dbset.ExecuteDeleteWhereAsync(e => e.InnerId == entity.InnerId, token), token);
}
public static List<TEntity> ListByArchiveId<TEntity>(this IAppDbService<TEntity> service, Guid archiveId)

View File

@@ -30,13 +30,7 @@ internal static class AppDbServiceCollectionExtension
public static ValueTask<List<TEntity>> ListAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
where TEntity : class
{
return service.ListAsync(query => query.Where(predicate), token);
}
public static ValueTask<List<TResult>> ListAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, IQueryable<TResult>> query, CancellationToken token = default)
where TEntity : class
{
return service.QueryAsync((query1, token) => query(query1).ToListAsync(token), token);
return service.QueryAsync((query, token) => query.Where(predicate).ToListAsync(token), token);
}
public static ObservableCollection<TEntity> ObservableCollection<TEntity>(this IAppDbService<TEntity> service)

View File

@@ -126,18 +126,6 @@ internal static class AppDbServiceExtension
return service.QueryAsync((query, token) => query.SingleAsync(predicate, token), token);
}
public static TEntity? SingleOrDefault<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
where TEntity : class
{
return service.Query(query => query.SingleOrDefault(predicate));
}
public static ValueTask<TEntity?> SingleOrDefaultAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
where TEntity : class
{
return service.QueryAsync((query, token) => query.SingleOrDefaultAsync(predicate, token), token);
}
public static int Update<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
where TEntity : class
{
@@ -156,21 +144,9 @@ internal static class AppDbServiceExtension
return service.Execute(dbset => dbset.RemoveAndSave(entity));
}
public static int Delete<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
where TEntity : class
{
return service.Execute(dbset => dbset.Where(predicate).ExecuteDelete());
}
public static ValueTask<int> DeleteAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => dbset.RemoveAndSaveAsync(entity, token), token);
}
public static ValueTask<int> DeleteAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate, CancellationToken token = default)
where TEntity : class
{
return service.ExecuteAsync((dbset, token) => dbset.Where(predicate).ExecuteDeleteAsync(token), token);
}
}

View File

@@ -46,12 +46,13 @@ internal sealed partial class AchievementDbService : IAchievementDbService
[SuppressMessage("", "CA1305")]
public ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take, CancellationToken token = default)
{
return this.ListAsync<EntityAchievement, EntityAchievement>(
query => query
return this.QueryAsync<EntityAchievement, List<EntityAchievement>>(
(query, token) => query
.Where(a => a.ArchiveId == archiveId)
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
.OrderByDescending(a => a.Time.ToString())
.Take(take),
.Take(take)
.ToListAsync(token),
token);
}

View File

@@ -12,5 +12,12 @@ namespace Snap.Hutao.Service.Announcement;
[HighQuality]
internal interface IAnnouncementService
{
ValueTask<AnnouncementWrapper> GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken token = default);
/// <summary>
/// 异步获取游戏公告与活动,通常会进行缓存
/// </summary>
/// <param name="languageCode">语言代码</param>
/// <param name="region">服务器</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>公告包装器</returns>
ValueTask<AnnouncementWrapper> GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken cancellationToken = default);
}

View File

@@ -15,6 +15,8 @@ namespace Snap.Hutao.Service;
[Injection(InjectAs.Singleton)]
internal sealed partial class AppOptions : DbStoreOptions
{
private bool? isAutoUploadGachaLogEnabled;
private bool? isAutoUploadSpiralAbyssRecordEnabled;
private bool? isEmptyHistoryWishVisible;
private bool? isUnobtainedWishItemVisible;
private BackdropType? backdropType;
@@ -23,6 +25,18 @@ internal sealed partial class AppOptions : DbStoreOptions
private Region? region;
private string? geetestCustomCompositeUrl;
public bool IsAutoUploadGachaLogEnabled
{
get => GetOption(ref isAutoUploadGachaLogEnabled, SettingEntry.IsAutoUploadGachaLogEnabled, false);
set => SetOption(ref isAutoUploadGachaLogEnabled, SettingEntry.IsAutoUploadGachaLogEnabled, value);
}
public bool IsAutoUploadSpiralAbyssRecordEnabled
{
get => GetOption(ref isAutoUploadSpiralAbyssRecordEnabled, SettingEntry.IsAutoUploadSpiralAbyssRecordEnabled, false);
set => SetOption(ref isAutoUploadSpiralAbyssRecordEnabled, SettingEntry.IsAutoUploadSpiralAbyssRecordEnabled, value);
}
public bool IsEmptyHistoryWishVisible
{
get => GetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible, false);

View File

@@ -29,11 +29,11 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
public void RemoveAvatarInfoRangeByUid(string uid)
{
this.Delete(i => i.Uid == uid);
this.Execute(dbset => dbset.Where(i => i.Uid == uid).ExecuteDelete());
}
public async ValueTask RemoveAvatarInfoRangeByUidAsync(string uid, CancellationToken token = default)
{
await this.DeleteAsync(i => i.Uid == uid, token).ConfigureAwait(false);
await this.ExecuteAsync((dbset, token) => dbset.Where(i => i.Uid == uid).ExecuteDeleteAsync(token), token).ConfigureAwait(false);
}
}

View File

@@ -25,11 +25,11 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
private readonly IMetadataService metadataService;
private readonly ISummaryFactory summaryFactory;
public async ValueTask<ValueResult<RefreshResultKind, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default)
public async ValueTask<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default)
{
if (!await metadataService.InitializeAsync().ConfigureAwait(false))
{
return new(RefreshResultKind.MetadataNotInitialized, null);
return new(RefreshResult.MetadataNotInitialized, null);
}
switch (refreshOption)
@@ -40,43 +40,43 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService
if (resp is null)
{
return new(RefreshResultKind.APIUnavailable, default);
return new(RefreshResult.APIUnavailable, default);
}
if (!string.IsNullOrEmpty(resp.Message))
{
return new(RefreshResultKind.StatusCodeNotSucceed, new Summary { Message = resp.Message });
return new(RefreshResult.StatusCodeNotSucceed, new Summary { Message = resp.Message });
}
if (!resp.IsValid)
{
return new(RefreshResultKind.ShowcaseNotOpen, default);
return new(RefreshResult.ShowcaseNotOpen, default);
}
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByShowcaseAsync(userAndUid.Uid.Value, resp.AvatarInfoList, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResultKind.Ok, summary);
return new(RefreshResult.Ok, summary);
}
case RefreshOption.RequestFromHoyolabGameRecord:
{
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResultKind.Ok, summary);
return new(RefreshResult.Ok, summary);
}
case RefreshOption.RequestFromHoyolabCalculate:
{
List<EntityAvatarInfo> list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndUid, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResultKind.Ok, summary);
return new(RefreshResult.Ok, summary);
}
default:
{
List<EntityAvatarInfo> list = await avatarInfoDbService.GetAvatarInfoListByUidAsync(userAndUid.Uid.Value, token).ConfigureAwait(false);
Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false);
return new(RefreshResultKind.Ok, summary.Avatars.Count == 0 ? null : summary);
return new(RefreshResult.Ok, summary.Avatars.Count == 0 ? null : summary);
}
}
}

View File

@@ -19,5 +19,5 @@ internal interface IAvatarInfoService
/// <param name="refreshOption">刷新选项</param>
/// <param name="token">取消令牌</param>
/// <returns>总览数据</returns>
ValueTask<ValueResult<RefreshResultKind, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default);
ValueTask<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default);
}

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.AvatarInfo;
/// 刷新结果
/// </summary>
[HighQuality]
internal enum RefreshResultKind
internal enum RefreshResult
{
/// <summary>
/// 正常

View File

@@ -26,11 +26,11 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
private HashSet<string>? currentBackgroundPathSet;
private HashSet<string> currentBackgroundPathSet;
public async ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous, CancellationToken token = default)
public async ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous)
{
HashSet<string> backgroundSet = await SkipOrInitBackgroundAsync(token).ConfigureAwait(false);
HashSet<string> backgroundSet = await SkipOrInitBackgroundAsync().ConfigureAwait(false);
if (backgroundSet.Count <= 0)
{
@@ -79,7 +79,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
}
}
private async ValueTask<HashSet<string>> SkipOrInitBackgroundAsync(CancellationToken token = default)
private async ValueTask<HashSet<string>> SkipOrInitBackgroundAsync()
{
switch (appOptions.BackgroundImageType)
{
@@ -90,7 +90,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
currentBackgroundPathSet = Directory
.EnumerateFiles(backgroundFolder, "*", SearchOption.AllDirectories)
.GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories)
.Where(path => AllowedFormats.Contains(Path.GetExtension(path)))
.ToHashSet();
}
@@ -100,13 +100,13 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
}
case BackgroundImageType.HutaoBing:
await SetCurrentBackgroundPathSetAsync((client, token) => client.GetBingWallpaperAsync(token), token).ConfigureAwait(false);
await SetCurrentBackgroundPathSetAsync(client => client.GetBingWallpaperAsync()).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoDaily:
await SetCurrentBackgroundPathSetAsync((client, token) => client.GetTodayWallpaperAsync(token), token).ConfigureAwait(false);
await SetCurrentBackgroundPathSetAsync(client => client.GetTodayWallpaperAsync()).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoOfficialLauncher:
await SetCurrentBackgroundPathSetAsync((client, token) => client.GetLauncherWallpaperAsync(token), token).ConfigureAwait(false);
await SetCurrentBackgroundPathSetAsync(client => client.GetLauncherWallpaperAsync()).ConfigureAwait(false);
break;
default:
currentBackgroundPathSet = [];
@@ -116,10 +116,10 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
currentBackgroundPathSet ??= [];
return currentBackgroundPathSet;
async Task SetCurrentBackgroundPathSetAsync(Func<HutaoWallpaperClient, CancellationToken, ValueTask<Response<Wallpaper>>> responseFactory, CancellationToken token = default)
async Task SetCurrentBackgroundPathSetAsync(Func<HutaoWallpaperClient, ValueTask<Response<Wallpaper>>> responseFactory)
{
HutaoWallpaperClient wallpaperClient = serviceProvider.GetRequiredService<HutaoWallpaperClient>();
Response<Wallpaper> response = await responseFactory(wallpaperClient, token).ConfigureAwait(false);
Response<Wallpaper> response = await responseFactory(wallpaperClient).ConfigureAwait(false);
if (response is { Data: Wallpaper wallpaper })
{
await taskContext.SwitchToMainThreadAsync();

View File

@@ -5,5 +5,5 @@ namespace Snap.Hutao.Service.BackgroundImage;
internal interface IBackgroundImageService
{
ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous, CancellationToken token = default);
ValueTask<ValueResult<bool, BackgroundImage?>> GetNextBackgroundImageAsync(BackgroundImage? previous);
}

View File

@@ -2,8 +2,9 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Model.Entity.Database;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Cultivation;
@@ -14,90 +15,182 @@ internal sealed partial class CultivationDbService : ICultivationDbService
{
private readonly IServiceProvider serviceProvider;
public IServiceProvider ServiceProvider { get => serviceProvider; }
public List<InventoryItem> GetInventoryItemListByProjectId(Guid projectId)
{
return this.List<InventoryItem>(i => i.ProjectId == projectId);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
IQueryable<InventoryItem> result = appDbContext.InventoryItems.AsNoTracking().Where(a => a.ProjectId == projectId);
return [.. result];
}
}
public ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId, CancellationToken token = default)
public async ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId)
{
return this.ListAsync<InventoryItem>(i => i.ProjectId == projectId, token);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.InventoryItems
.AsNoTracking()
.Where(a => a.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
}
}
public ValueTask<List<CultivateEntry>> GetCultivateEntryListByProjectIdAsync(Guid projectId, CancellationToken token = default)
public async ValueTask<List<CultivateEntry>> GetCultivateEntryListByProjectIdAsync(Guid projectId)
{
return this.ListAsync<CultivateEntry>(e => e.ProjectId == projectId, token);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.CultivateEntries
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
}
}
public ValueTask<List<CultivateEntry>> GetCultivateEntryListIncludingLevelInformationByProjectIdAsync(Guid projectId, CancellationToken token = default)
public async ValueTask<List<CultivateEntry>> GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(Guid projectId)
{
return this.ListAsync<CultivateEntry, CultivateEntry>(query => query.Where(e => e.ProjectId == projectId).Include(e => e.LevelInformation), token);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.CultivateEntries
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.Include(e => e.LevelInformation)
.ToListAsync()
.ConfigureAwait(false);
}
}
public ValueTask<List<CultivateItem>> GetCultivateItemListByEntryIdAsync(Guid entryId, CancellationToken token = default)
public async ValueTask<List<CultivateItem>> GetCultivateItemListByEntryIdAsync(Guid entryId)
{
return this.ListAsync<CultivateItem, CultivateItem>(query => query.Where(i => i.EntryId == entryId).OrderBy(i => i.ItemId), token);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.CultivateItems
.Where(i => i.EntryId == entryId)
.OrderBy(i => i.ItemId)
.ToListAsync()
.ConfigureAwait(false);
}
}
public async ValueTask RemoveCultivateEntryByIdAsync(Guid entryId, CancellationToken token = default)
public async ValueTask RemoveCultivateEntryByIdAsync(Guid entryId)
{
await this.DeleteByInnerIdAsync<CultivateEntry>(entryId, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateEntries
.ExecuteDeleteWhereAsync(i => i.InnerId == entryId)
.ConfigureAwait(false);
}
}
public void UpdateCultivateItem(CultivateItem item)
{
this.Update(item);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.CultivateItems.UpdateAndSave(item);
}
}
public async ValueTask UpdateCultivateItemAsync(CultivateItem item, CancellationToken token = default)
public async ValueTask UpdateCultivateItemAsync(CultivateItem item)
{
await this.UpdateAsync(item, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateItems.UpdateAndSaveAsync(item).ConfigureAwait(false);
}
}
public async ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId, CancellationToken token = default)
public async ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId)
{
return await this.SingleOrDefaultAsync<CultivateEntry>(e => e.ProjectId == projectId && e.Id == itemId, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.CultivateEntries
.SingleOrDefaultAsync(e => e.ProjectId == projectId && e.Id == itemId)
.ConfigureAwait(false);
}
}
public async ValueTask AddCultivateEntryAsync(CultivateEntry entry, CancellationToken token = default)
public async ValueTask AddCultivateEntryAsync(CultivateEntry entry)
{
await this.AddAsync(entry, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateEntries.AddAndSaveAsync(entry).ConfigureAwait(false);
}
}
public async ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId, CancellationToken token = default)
public async ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId)
{
await this.DeleteAsync<CultivateItem>(i => i.EntryId == entryId, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateItems
.ExecuteDeleteWhereAsync(i => i.EntryId == entryId)
.ConfigureAwait(false);
}
}
public async ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd, CancellationToken token = default)
public async ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd)
{
await this.AddRangeAsync(toAdd, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateItems.AddRangeAndSaveAsync(toAdd).ConfigureAwait(false);
}
}
public async ValueTask AddCultivateProjectAsync(CultivateProject project, CancellationToken token = default)
public async ValueTask AddCultivateProjectAsync(CultivateProject project)
{
await this.AddAsync(project, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateProjects.AddAndSaveAsync(project).ConfigureAwait(false);
}
}
public async ValueTask RemoveCultivateProjectByIdAsync(Guid projectId, CancellationToken token = default)
public async ValueTask RemoveCultivateProjectByIdAsync(Guid projectId)
{
await this.DeleteByInnerIdAsync<CultivateProject>(projectId, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateProjects
.ExecuteDeleteWhereAsync(p => p.InnerId == projectId)
.ConfigureAwait(false);
}
}
public ObservableCollection<CultivateProject> GetCultivateProjectCollection()
{
return this.ObservableCollection<CultivateProject>();
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.CultivateProjects.AsNoTracking().ToObservableCollection();
}
}
public async ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId, CancellationToken token = default)
public async ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId)
{
await this.DeleteAsync<CultivateEntryLevelInformation>(l => l.EntryId == entryId, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.LevelInformations.ExecuteDeleteWhereAsync(l => l.EntryId == entryId).ConfigureAwait(false);
}
}
public async ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation, CancellationToken token = default)
public async ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation)
{
await this.AddAsync(levelInformation, token).ConfigureAwait(false);
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.LevelInformations.AddAndSaveAsync(levelInformation).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,79 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Cultivation;
/// <summary>
/// 集合部分
/// </summary>
internal sealed partial class CultivationService
{
private ObservableCollection<CultivateProject>? projects;
/// <inheritdoc/>
public CultivateProject? Current
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
/// <inheritdoc/>
public ObservableCollection<CultivateProject> ProjectCollection
{
get
{
if (projects is null)
{
projects = cultivationDbService.GetCultivateProjectCollection();
Current ??= projects.SelectedOrDefault();
}
return projects;
}
}
/// <inheritdoc/>
public async ValueTask<ProjectAddResult> TryAddProjectAsync(CultivateProject project)
{
if (string.IsNullOrWhiteSpace(project.Name))
{
return ProjectAddResult.InvalidName;
}
ArgumentNullException.ThrowIfNull(projects);
if (projects.Any(a => a.Name == project.Name))
{
return ProjectAddResult.AlreadyExists;
}
// Sync cache
await taskContext.SwitchToMainThreadAsync();
projects.Add(project);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.AddCultivateProjectAsync(project).ConfigureAwait(false);
return ProjectAddResult.Added;
}
/// <inheritdoc/>
public async ValueTask RemoveProjectAsync(CultivateProject project)
{
ArgumentNullException.ThrowIfNull(projects);
// Sync cache
// Keep this on main thread.
await taskContext.SwitchToMainThreadAsync();
projects.Remove(project);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.RemoveCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false);
}
}

View File

@@ -4,13 +4,13 @@
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Service.Inventory;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.Cultivation;
using System.Collections.ObjectModel;
using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
namespace Snap.Hutao.Service.Cultivation;
@@ -25,46 +25,28 @@ internal sealed partial class CultivationService : ICultivationService
private readonly ScopedDbCurrent<CultivateProject, Message.CultivateProjectChangedMessage> dbCurrent;
private readonly ICultivationDbService cultivationDbService;
private readonly IInventoryDbService inventoryDbService;
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private ObservableCollection<CultivateProject>? projects;
/// <inheritdoc/>
public CultivateProject? Current
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
/// <inheritdoc/>
public ObservableCollection<CultivateProject> ProjectCollection
{
get
{
if (projects is null)
{
projects = cultivationDbService.GetCultivateProjectCollection();
Current ??= projects.SelectedOrDefault();
}
return projects;
}
}
/// <inheritdoc/>
public List<InventoryItemView> GetInventoryItemViews(CultivateProject cultivateProject, ICultivationMetadataContext context, ICommand saveCommand)
{
Guid projectId = cultivateProject.InnerId;
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
List<InventoryItemView> results = [];
foreach (Material meta in context.EnumerateInventoryMaterial())
using (IServiceScope scope = serviceProvider.CreateScope())
{
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id);
results.Add(new(entity, meta, saveCommand));
}
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return results;
Guid projectId = cultivateProject.InnerId;
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
List<InventoryItemView> results = [];
foreach (Material meta in context.EnumerateInventoryMaterial())
{
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id);
results.Add(new(entity, meta, saveCommand));
}
return results;
}
}
/// <inheritdoc/>
@@ -72,15 +54,13 @@ internal sealed partial class CultivationService : ICultivationService
{
await taskContext.SwitchToBackgroundAsync();
List<CultivateEntry> entries = await cultivationDbService
.GetCultivateEntryListIncludingLevelInformationByProjectIdAsync(cultivateProject.InnerId)
.GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(cultivateProject.InnerId)
.ConfigureAwait(false);
List<CultivateEntryView> resultEntries = new(entries.Count);
foreach (CultivateEntry entry in entries)
{
List<CultivateItemView> entryItems = [];
// Async operation here, thus we can't use the Span trick.
foreach (CultivateItem cultivateItem in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false))
{
entryItems.Add(new(cultivateItem, context.GetMaterial(cultivateItem.ItemId)));
@@ -98,7 +78,9 @@ internal sealed partial class CultivationService : ICultivationService
resultEntries.Add(new(entry, item, entryItems));
}
return resultEntries.SortByDescending(e => e.IsToday).ToObservableCollection();
return resultEntries
.OrderByDescending(e => e.IsToday)
.ToObservableCollection();
}
/// <inheritdoc/>
@@ -110,9 +92,11 @@ internal sealed partial class CultivationService : ICultivationService
Guid projectId = cultivateProject.InnerId;
foreach (CultivateEntry entry in await cultivationDbService.GetCultivateEntryListByProjectIdAsync(projectId, token).ConfigureAwait(false))
token.ThrowIfCancellationRequested();
foreach (CultivateEntry entry in await cultivationDbService.GetCultivateEntryListByProjectIdAsync(projectId).ConfigureAwait(false))
{
foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId, token).ConfigureAwait(false))
foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false))
{
if (item.IsFinished)
{
@@ -130,7 +114,9 @@ internal sealed partial class CultivationService : ICultivationService
}
}
foreach (InventoryItem inventoryItem in await cultivationDbService.GetInventoryItemListByProjectIdAsync(projectId, token).ConfigureAwait(false))
token.ThrowIfCancellationRequested();
foreach (InventoryItem inventoryItem in await cultivationDbService.GetInventoryItemListByProjectIdAsync(projectId).ConfigureAwait(false))
{
if (resultItems.SingleOrDefault(i => i.Inner.Id == inventoryItem.ItemId) is { } existedItem)
{
@@ -138,12 +124,15 @@ internal sealed partial class CultivationService : ICultivationService
}
}
token.ThrowIfCancellationRequested();
return resultItems.SortBy(item => item.Inner.Id, MaterialIdComparer.Shared).ToObservableCollection();
}
/// <inheritdoc/>
public async ValueTask RemoveCultivateEntryAsync(Guid entryId)
{
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.RemoveCultivateEntryByIdAsync(entryId).ConfigureAwait(false);
}
@@ -160,7 +149,7 @@ internal sealed partial class CultivationService : ICultivationService
}
/// <inheritdoc/>
public async ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<CalculateItem> items, LevelInformation levelInformation)
public async ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items, LevelInformation levelInformation)
{
if (items.Count == 0)
{
@@ -201,45 +190,4 @@ internal sealed partial class CultivationService : ICultivationService
return true;
}
/// <inheritdoc/>
public async ValueTask<ProjectAddResultKind> TryAddProjectAsync(CultivateProject project)
{
if (string.IsNullOrWhiteSpace(project.Name))
{
return ProjectAddResultKind.InvalidName;
}
ArgumentNullException.ThrowIfNull(projects);
if (projects.Any(a => a.Name == project.Name))
{
return ProjectAddResultKind.AlreadyExists;
}
// Sync cache
await taskContext.SwitchToMainThreadAsync();
projects.Add(project);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.AddCultivateProjectAsync(project).ConfigureAwait(false);
return ProjectAddResultKind.Added;
}
/// <inheritdoc/>
public async ValueTask RemoveProjectAsync(CultivateProject project)
{
ArgumentNullException.ThrowIfNull(projects);
// Sync cache
// Keep this on main thread.
await taskContext.SwitchToMainThreadAsync();
projects.Remove(project);
// Sync database
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.RemoveCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false);
}
}

View File

@@ -2,48 +2,43 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Cultivation;
internal interface ICultivationDbService : IAppDbService<InventoryItem>,
IAppDbService<CultivateEntryLevelInformation>,
IAppDbService<CultivateProject>,
IAppDbService<CultivateEntry>,
IAppDbService<CultivateItem>
internal interface ICultivationDbService
{
ValueTask AddCultivateProjectAsync(CultivateProject project, CancellationToken token = default);
ValueTask AddCultivateProjectAsync(CultivateProject project);
ValueTask RemoveCultivateEntryByIdAsync(Guid entryId, CancellationToken token = default);
ValueTask RemoveCultivateEntryByIdAsync(Guid entryId);
ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId, CancellationToken token = default);
ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId);
ValueTask RemoveCultivateProjectByIdAsync(Guid projectId, CancellationToken token = default);
ValueTask RemoveCultivateProjectByIdAsync(Guid projectId);
ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId, CancellationToken token = default);
ValueTask<CultivateEntry?> GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId);
ValueTask<List<CultivateEntry>> GetCultivateEntryListByProjectIdAsync(Guid projectId, CancellationToken token = default);
ValueTask<List<CultivateEntry>> GetCultivateEntryListByProjectIdAsync(Guid projectId);
ValueTask<List<CultivateItem>> GetCultivateItemListByEntryIdAsync(Guid entryId, CancellationToken token = default);
ValueTask<List<CultivateItem>> GetCultivateItemListByEntryIdAsync(Guid entryId);
ObservableCollection<CultivateProject> GetCultivateProjectCollection();
List<InventoryItem> GetInventoryItemListByProjectId(Guid projectId);
ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId, CancellationToken token = default);
ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId);
ValueTask AddCultivateEntryAsync(CultivateEntry entry, CancellationToken token = default);
ValueTask AddCultivateEntryAsync(CultivateEntry entry);
ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd, CancellationToken token = default);
ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd);
void UpdateCultivateItem(CultivateItem item);
ValueTask UpdateCultivateItemAsync(CultivateItem item, CancellationToken token = default);
ValueTask UpdateCultivateItemAsync(CultivateItem item);
ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId, CancellationToken token = default);
ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId);
ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation, CancellationToken token = default);
ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation);
ValueTask<List<CultivateEntry>> GetCultivateEntryListIncludingLevelInformationByProjectIdAsync(Guid projectId, CancellationToken token = default);
ValueTask<List<CultivateEntry>> GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(Guid projectId);
}

View File

@@ -65,5 +65,5 @@ internal interface ICultivationService
/// </summary>
/// <param name="project">项目</param>
/// <returns>添加操作的结果</returns>
ValueTask<ProjectAddResultKind> TryAddProjectAsync(CultivateProject project);
ValueTask<ProjectAddResult> TryAddProjectAsync(CultivateProject project);
}

View File

@@ -8,10 +8,9 @@ namespace Snap.Hutao.Service.Cultivation;
internal sealed class MaterialIdComparer : IComparer<MaterialId>
{
private static MaterialIdComparer? shared;
private static object? syncRoot;
private static readonly Lazy<MaterialIdComparer> LazyShared = new(() => new());
public static MaterialIdComparer Shared { get => LazyInitializer.EnsureInitialized(ref shared, ref syncRoot, () => new()); }
public static MaterialIdComparer Shared { get => LazyShared.Value; }
public int Compare(MaterialId x, MaterialId y)
{

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.Cultivation;
/// 项目添加结果
/// </summary>
[HighQuality]
internal enum ProjectAddResultKind
internal enum ProjectAddResult
{
/// <summary>
/// 添加成功

View File

@@ -25,7 +25,7 @@ internal sealed partial class DailyNoteNotificationOperation
private readonly ITaskContext taskContext;
private readonly IGameServiceFacade gameService;
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly BindingClient bindingClient;
private readonly DailyNoteOptions options;
private readonly RuntimeOptions runtimeOptions;
@@ -58,19 +58,14 @@ internal sealed partial class DailyNoteNotificationOperation
}
else
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
Response<ListWrapper<UserGameRole>> rolesResponse = await bindingClient
.GetUserGameRolesOverseaAwareAsync(entry.User)
.ConfigureAwait(false);
if (rolesResponse.IsOk())
{
BindingClient bindingClient = scope.ServiceProvider.GetRequiredService<BindingClient>();
Response<ListWrapper<UserGameRole>> rolesResponse = await bindingClient
.GetUserGameRolesOverseaAwareAsync(entry.User)
.ConfigureAwait(false);
if (rolesResponse.IsOk())
{
List<UserGameRole> roles = rolesResponse.Data.List;
attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? ToastAttributionUnknown;
}
List<UserGameRole> roles = rolesResponse.Data.List;
attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? ToastAttributionUnknown;
}
}

View File

@@ -181,7 +181,6 @@
<None Remove="View\Dialog\LaunchGameConfigurationFixDialog.xaml" />
<None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" />
<None Remove="View\Dialog\ReconfirmDialog.xaml" />
<None Remove="View\Dialog\SpiralAbyssUploadRecordHomaNotLoginDialog.xaml" />
<None Remove="View\Dialog\UpdatePackageDownloadConfirmDialog.xaml" />
<None Remove="View\Dialog\UserDialog.xaml" />
<None Remove="View\Dialog\UserQRCodeDialog.xaml" />
@@ -355,11 +354,6 @@
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\SpiralAbyssUploadRecordHomaNotLoginDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\UpdatePackageDownloadConfirmDialog.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -1,16 +0,0 @@
<ContentDialog
x:Class="Snap.Hutao.View.Dialog.SpiralAbyssUploadRecordHomaNotLoginDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
Title="{shcm:ResourceString Name=ViewDialogSpiralAbyssUploadRecordHomaNotLoginTitle}"
CloseButtonText="{shcm:ResourceString Name=ViewDialogSpiralAbyssUploadRecordHomaNotLoginCloseButtonText}"
DefaultButton="Close"
PrimaryButtonText="{shcm:ResourceString Name=ViewDialogSpiralAbyssUploadRecordHomaNotLoginPrimaryButtonText}"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<TextBlock Text="{shcm:ResourceString Name=ViewDialogSpiralAbyssUploadRecordHomaNotLoginHint}"/>
</ContentDialog>

View File

@@ -1,24 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.View.Dialog;
internal sealed partial class SpiralAbyssUploadRecordHomaNotLoginDialog : ContentDialog
{
private readonly ITaskContext taskContext;
public SpiralAbyssUploadRecordHomaNotLoginDialog(IServiceProvider serviceProvider)
{
InitializeComponent();
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
}
public async ValueTask<bool> ConfirmAsync()
{
await taskContext.SwitchToMainThreadAsync();
return await ShowAsync() is ContentDialogResult.Primary;
}
}

View File

@@ -265,6 +265,22 @@
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
</ScrollViewer>
<ScrollViewer>
<cwc:SettingsExpander
Description="{shcm:ResourceString Name=ViewPageSettingHutaoCloudAutoUploadDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingHutaoCloudAutoUploadHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE898;}"
IsExpanded="True">
<cwc:SettingsExpander.Items>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageSettingHutaoCloudGachaLogAutoUploadDescription}" Header="{shcm:ResourceString Name=ViewPageSettingHutaoCloudGachaLogAutoUploadHeader}">
<ToggleSwitch IsOn="{Binding AppOptions.IsAutoUploadGachaLogEnabled, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageSettingHutaoCloudSpiralAbyssAutoUploadDescription}" Header="{shcm:ResourceString Name=ViewPageSettingHutaoCloudSpiralAbyssAutoUploadHeader}">
<ToggleSwitch IsOn="{Binding AppOptions.IsAutoUploadSpiralAbyssRecordEnabled, Mode=TwoWay}"/>
</cwc:SettingsCard>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
</ScrollViewer>
</StackPanel>
</Border>
</Border>

View File

@@ -120,7 +120,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
{
try
{
ValueResult<RefreshResultKind, Summary?> summaryResult;
ValueResult<RefreshResult, Summary?> summaryResult;
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
ContentDialog dialog = await contentDialogFactory
@@ -135,8 +135,8 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
}
}
(RefreshResultKind result, Summary? summary) = summaryResult;
if (result == RefreshResultKind.Ok)
(RefreshResult result, Summary? summary) = summaryResult;
if (result == RefreshResult.Ok)
{
await taskContext.SwitchToMainThreadAsync();
Summary = summary;
@@ -146,16 +146,16 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
{
switch (result)
{
case RefreshResultKind.APIUnavailable:
case RefreshResult.APIUnavailable:
infoBarService.Warning(SH.ViewModelAvatarPropertyEnkaApiUnavailable);
break;
case RefreshResultKind.StatusCodeNotSucceed:
case RefreshResult.StatusCodeNotSucceed:
ArgumentNullException.ThrowIfNull(summary);
infoBarService.Warning(summary.Message);
break;
case RefreshResultKind.ShowcaseNotOpen:
case RefreshResult.ShowcaseNotOpen:
infoBarService.Warning(SH.ViewModelAvatarPropertyShowcaseNotOpen);
break;
}

View File

@@ -86,15 +86,15 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
switch (await cultivationService.TryAddProjectAsync(project).ConfigureAwait(false))
{
case ProjectAddResultKind.Added:
case ProjectAddResult.Added:
infoBarService.Success(SH.ViewModelCultivationProjectAdded);
await taskContext.SwitchToMainThreadAsync();
SelectedProject = project;
break;
case ProjectAddResultKind.InvalidName:
case ProjectAddResult.InvalidName:
infoBarService.Information(SH.ViewModelCultivationProjectInvalidName);
break;
case ProjectAddResultKind.AlreadyExists:
case ProjectAddResult.AlreadyExists:
infoBarService.Information(SH.ViewModelCultivationProjectAlreadyExists);
break;
default:

View File

@@ -11,6 +11,7 @@ using Snap.Hutao.Factory.Picker;
using Snap.Hutao.Factory.Progress;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.InterChange.GachaLog;
using Snap.Hutao.Service;
using Snap.Hutao.Service.GachaLog;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Notification;
@@ -38,6 +39,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
private readonly IInfoBarService infoBarService;
private readonly JsonSerializerOptions options;
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
private ObservableCollection<GachaArchive>? archives;
private GachaArchive? selectedArchive;
@@ -198,6 +200,11 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
{
await SetSelectedArchiveAndUpdateStatisticsAsync(gachaLogService.CurrentArchive, true).ConfigureAwait(false);
await hideToken.DisposeAsync().ConfigureAwait(false);
if (HutaoCloudViewModel.Options.IsCloudServiceAllowed && appOptions.IsAutoUploadGachaLogEnabled)
{
await HutaoCloudViewModel.UploadAsync(gachaLogService.CurrentArchive).ConfigureAwait(false);
}
}
else
{

View File

@@ -47,6 +47,35 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
/// </summary>
internal ICommand RetrieveCommand { get; set; }
[Command("UploadCommand")]
internal async Task UploadAsync(GachaArchive? gachaArchive)
{
if (gachaArchive is not null)
{
ContentDialog dialog = await contentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelGachaLogUploadToHutaoCloudProgress)
.ConfigureAwait(false);
bool isOk;
string message;
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
{
(isOk, message) = await hutaoCloudService.UploadGachaItemsAsync(gachaArchive).ConfigureAwait(false);
}
if (isOk)
{
infoBarService.Success(message);
await RefreshUidCollectionAsync().ConfigureAwait(false);
}
else
{
infoBarService.Warning(message);
}
}
}
/// <summary>
/// 异步获取祈愿记录
/// </summary>
@@ -77,35 +106,6 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
await Windows.System.Launcher.LaunchUriAsync("https://afdian.net/item/80d3b9decf9011edb5f452540025c377".ToUri());
}
[Command("UploadCommand")]
private async Task UploadAsync(GachaArchive? gachaArchive)
{
if (gachaArchive is not null)
{
ContentDialog dialog = await contentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelGachaLogUploadToHutaoCloudProgress)
.ConfigureAwait(false);
bool isOk;
string message;
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
{
(isOk, message) = await hutaoCloudService.UploadGachaItemsAsync(gachaArchive).ConfigureAwait(false);
}
if (isOk)
{
infoBarService.Success(message);
await RefreshUidCollectionAsync().ConfigureAwait(false);
}
else
{
infoBarService.Warning(message);
}
}
}
[Command("DeleteCommand")]
private async Task DeleteAsync(string? uid)
{

View File

@@ -5,13 +5,11 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Message;
using Snap.Hutao.Service;
using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.SpiralAbyss;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.View.Page;
using Snap.Hutao.ViewModel.Complex;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hutao.Response;
@@ -32,12 +30,12 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
private readonly ISpiralAbyssRecordService spiralAbyssRecordService;
private readonly IContentDialogFactory contentDialogFactory;
private readonly HutaoSpiralAbyssClient spiralAbyssClient;
private readonly INavigationService navigationService;
private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext;
private readonly IUserService userService;
private readonly HutaoDatabaseViewModel hutaoDatabaseViewModel;
private readonly HutaoUserOptions hutaoUserOptions;
private readonly AppOptions appOptions;
private ObservableCollection<SpiralAbyssView>? spiralAbyssEntries;
private SpiralAbyssView? selectedView;
@@ -128,6 +126,11 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
await taskContext.SwitchToMainThreadAsync();
SelectedView = SpiralAbyssEntries.FirstOrDefault(s => s.Engaged);
if (hutaoUserOptions.IsLoggedIn && appOptions.IsAutoUploadSpiralAbyssRecordEnabled)
{
await UploadSpiralAbyssRecordCoreAsync(userAndUid).ConfigureAwait(false);
}
}
}
}
@@ -139,42 +142,45 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
{
if (!hutaoUserOptions.IsLoggedIn)
{
SpiralAbyssUploadRecordHomaNotLoginDialog dialog = await contentDialogFactory
.CreateInstanceAsync<SpiralAbyssUploadRecordHomaNotLoginDialog>()
ContentDialogResult result = await contentDialogFactory
.CreateForConfirmCancelAsync(SH.ViewModelSpiralAbyssUploadRecordHomaNotLoginTitle, SH.ViewModelSpiralAbyssUploadRecordHomaNotLoginContent)
.ConfigureAwait(false);
if (!await dialog.ConfirmAsync().ConfigureAwait(false))
if (result is not ContentDialogResult.Primary)
{
await taskContext.SwitchToMainThreadAsync();
await navigationService.NavigateAsync<SettingPage>(INavigationAwaiter.Default, true).ConfigureAwait(false);
return;
}
}
SimpleRecord? record = await spiralAbyssClient.GetPlayerRecordAsync(userAndUid).ConfigureAwait(false);
if (record is not null)
{
Web.Response.Response response = await spiralAbyssClient.UploadRecordAsync(record).ConfigureAwait(false);
if (response is { ReturnCode: 0 })
{
if (response is ILocalizableResponse localizableResponse)
{
infoBarService.Success(localizableResponse.GetLocalizationMessage());
}
}
else
{
if (response is ILocalizableResponse localizableResponse)
{
infoBarService.Warning(localizableResponse.GetLocalizationMessage());
}
}
}
await UploadSpiralAbyssRecordCoreAsync(userAndUid).ConfigureAwait(false);
}
else
{
infoBarService.Warning(SH.MustSelectUserAndUid);
}
}
}
private async ValueTask UploadSpiralAbyssRecordCoreAsync(UserAndUid userAndUid)
{
SimpleRecord? record = await spiralAbyssClient.GetPlayerRecordAsync(userAndUid).ConfigureAwait(false);
if (record is not null)
{
Web.Response.Response response = await spiralAbyssClient.UploadRecordAsync(record).ConfigureAwait(false);
if (response is { ReturnCode: 0 })
{
if (response is ILocalizableResponse localizableResponse)
{
infoBarService.Success(localizableResponse.GetLocalizationMessage());
}
}
else
{
if (response is ILocalizableResponse localizableResponse)
{
infoBarService.Warning(localizableResponse.GetLocalizationMessage());
}
}
}
}
}