mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
[skip ci] uigf 4 support part 4
This commit is contained in:
@@ -18,9 +18,8 @@ internal static class ValueFileExtension
|
||||
return new(true, t);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
_ = ex;
|
||||
return new(false, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ namespace Snap.Hutao.Model.Entity;
|
||||
internal sealed class Achievement : IAppDbEntityHasArchive,
|
||||
IEquatable<Achievement>,
|
||||
IDbMappingForeignKeyFrom<Achievement, AchievementId>,
|
||||
IDbMappingForeignKeyFrom<Achievement, UIAFItem>,
|
||||
IDbMappingForeignKeyFrom<Achievement, HutaoReservedAchievement>
|
||||
IDbMappingForeignKeyFrom<Achievement, UIAFItem>
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
@@ -58,18 +57,6 @@ internal sealed class Achievement : IAppDbEntityHasArchive,
|
||||
};
|
||||
}
|
||||
|
||||
public static Achievement From(Guid archiveId, HutaoReservedAchievement achievement)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ArchiveId = archiveId,
|
||||
Id = achievement.Id,
|
||||
Current = achievement.Current,
|
||||
Status = achievement.Status,
|
||||
Time = achievement.Time,
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(Achievement? other)
|
||||
{
|
||||
if (other is null)
|
||||
|
||||
@@ -27,7 +27,7 @@ internal sealed class CultivateEntry : IAppDbEntity,
|
||||
|
||||
public uint Id { get; set; }
|
||||
|
||||
public static CultivateEntry From(in Guid projectId, in CultivateType type, in uint id)
|
||||
public static CultivateEntry From(Guid projectId, CultivateType type, uint id)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ internal sealed class CultivateItem : IDbMappingForeignKeyFrom<CultivateItem, We
|
||||
|
||||
public bool IsFinished { get; set; }
|
||||
|
||||
public static CultivateItem From(in Guid entryId, in Web.Hoyolab.Takumi.Event.Calculate.Item item)
|
||||
public static CultivateItem From(Guid entryId, Web.Hoyolab.Takumi.Event.Calculate.Item item)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity.Abstraction;
|
||||
using Snap.Hutao.Model.InterChange;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
@@ -14,8 +13,6 @@ namespace Snap.Hutao.Model.Entity;
|
||||
[Table("gacha_items")]
|
||||
internal sealed partial class GachaItem
|
||||
: IDbMappingForeignKeyFrom<GachaItem, GachaLogItem, uint>,
|
||||
IDbMappingForeignKeyFrom<GachaItem, LegacyUIGFItem, uint>,
|
||||
IDbMappingForeignKeyFrom<GachaItem, LegacyUIGFItem>,
|
||||
IDbMappingForeignKeyFrom<GachaItem, Web.Hutao.GachaLog.GachaItem>,
|
||||
IDbMappingForeignKeyFrom<GachaItem, Hk4eItem, int>
|
||||
{
|
||||
@@ -51,32 +48,6 @@ internal sealed partial class GachaItem
|
||||
};
|
||||
}
|
||||
|
||||
public static GachaItem From(Guid archiveId, LegacyUIGFItem item, uint itemId)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ArchiveId = archiveId,
|
||||
GachaType = item.GachaType,
|
||||
QueryType = item.UIGFGachaType,
|
||||
ItemId = itemId,
|
||||
Time = item.Time,
|
||||
Id = item.Id,
|
||||
};
|
||||
}
|
||||
|
||||
public static GachaItem From(Guid archiveId, LegacyUIGFItem item)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ArchiveId = archiveId,
|
||||
GachaType = item.GachaType,
|
||||
QueryType = item.UIGFGachaType,
|
||||
ItemId = uint.Parse(item.ItemId, CultureInfo.CurrentCulture),
|
||||
Time = item.Time,
|
||||
Id = item.Id,
|
||||
};
|
||||
}
|
||||
|
||||
public static GachaItem From(Guid archiveId, Web.Hutao.GachaLog.GachaItem item)
|
||||
{
|
||||
return new()
|
||||
|
||||
@@ -15,41 +15,20 @@ namespace Snap.Hutao.Model.Entity;
|
||||
internal sealed class InventoryItem : IDbMappingForeignKeyFrom<InventoryItem, uint>,
|
||||
IDbMappingForeignKeyFrom<InventoryItem, uint, uint>
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部Id
|
||||
/// </summary>
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid InnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 培养计划Id
|
||||
/// </summary>
|
||||
public Guid ProjectId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属的计划
|
||||
/// </summary>
|
||||
[ForeignKey(nameof(ProjectId))]
|
||||
public CultivateProject Project { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 物品Id
|
||||
/// </summary>
|
||||
public uint ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 个数 4294967295
|
||||
/// </summary>
|
||||
public uint Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的个数为0的物品
|
||||
/// </summary>
|
||||
/// <param name="projectId">项目Id</param>
|
||||
/// <param name="itemId">物品Id</param>
|
||||
/// <returns>新的个数为0的物品</returns>
|
||||
public static InventoryItem From(in Guid projectId, in uint itemId)
|
||||
public static InventoryItem From(Guid projectId, uint itemId)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
@@ -58,14 +37,7 @@ internal sealed class InventoryItem : IDbMappingForeignKeyFrom<InventoryItem, ui
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的个数不为0的物品
|
||||
/// </summary>
|
||||
/// <param name="projectId">项目Id</param>
|
||||
/// <param name="itemId">物品Id</param>
|
||||
/// <param name="count">物品个数</param>
|
||||
/// <returns>新的个数不为0的物品</returns>
|
||||
public static InventoryItem From(in Guid projectId, in uint itemId, in uint count)
|
||||
public static InventoryItem From(Guid projectId, uint itemId, uint count)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ using Snap.Hutao.Core.Json.Annotation;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
internal sealed class Hk4eItem : IMappingFrom<Hk4eItem, GachaItem>
|
||||
{
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
/// <summary>
|
||||
/// 统一可交换祈愿格式
|
||||
/// https://uigf.org/standards/UIGF.html
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete]
|
||||
internal sealed class LegacyUIGF : IJsonOnSerializing, IJsonOnDeserialized
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前版本
|
||||
/// </summary>
|
||||
public const string CurrentVersion = "v3.0";
|
||||
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
[JsonRequired]
|
||||
[JsonPropertyName("info")]
|
||||
public LegacyUIGFInfo Info { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("list")]
|
||||
public List<LegacyUIGFItem> List { get; set; } = default!;
|
||||
|
||||
public void OnSerializing()
|
||||
{
|
||||
TimeSpan offset = GetRegionTimeZoneUtcOffset();
|
||||
|
||||
foreach (LegacyUIGFItem item in List)
|
||||
{
|
||||
item.Time = item.Time.ToOffset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDeserialized()
|
||||
{
|
||||
// Adjust items timezone
|
||||
TimeSpan offset = GetRegionTimeZoneUtcOffset();
|
||||
|
||||
foreach (LegacyUIGFItem item in List)
|
||||
{
|
||||
item.Time = UnsafeDateTimeOffset.AdjustOffsetOnly(item.Time, offset);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCurrentVersionSupported(out LegacyUIGFVersion version)
|
||||
{
|
||||
version = Info.UIGFVersion switch
|
||||
{
|
||||
"v2.1" => LegacyUIGFVersion.Major2Minor2OrLower,
|
||||
"v2.2" => LegacyUIGFVersion.Major2Minor2OrLower,
|
||||
"v2.3" => LegacyUIGFVersion.Major2Minor3OrHigher,
|
||||
"v2.4" => LegacyUIGFVersion.Major2Minor3OrHigher,
|
||||
"v3.0" => LegacyUIGFVersion.Major2Minor3OrHigher,
|
||||
_ => LegacyUIGFVersion.NotSupported,
|
||||
};
|
||||
|
||||
return version != LegacyUIGFVersion.NotSupported;
|
||||
}
|
||||
|
||||
public bool IsMajor2Minor2OrLowerListValid([NotNullWhen(false)] out long id)
|
||||
{
|
||||
foreach (ref readonly LegacyUIGFItem item in CollectionsMarshal.AsSpan(List))
|
||||
{
|
||||
if (item.ItemType != SH.ModelInterchangeUIGFItemTypeAvatar && item.ItemType != SH.ModelInterchangeUIGFItemTypeWeapon)
|
||||
{
|
||||
id = item.Id;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
id = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsMajor2Minor3OrHigherListValid([NotNullWhen(false)] out long id)
|
||||
{
|
||||
foreach (ref readonly LegacyUIGFItem item in CollectionsMarshal.AsSpan(List))
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.ItemId))
|
||||
{
|
||||
id = item.Id;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
id = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private TimeSpan GetRegionTimeZoneUtcOffset()
|
||||
{
|
||||
if (Info.RegionTimeZone is int offsetHours)
|
||||
{
|
||||
return new TimeSpan(offsetHours, 0, 0);
|
||||
}
|
||||
|
||||
return PlayerUid.GetRegionTimeZoneUtcOffsetForUid(Info.Uid);
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
/// <summary>
|
||||
/// UIGF格式的信息
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete]
|
||||
internal sealed class LegacyUIGFInfo : IMappingFrom<LegacyUIGFInfo, RuntimeOptions, CultureOptions, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户Uid
|
||||
/// </summary>
|
||||
[JsonPropertyName("uid")]
|
||||
public string Uid { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 语言
|
||||
/// </summary>
|
||||
[JsonPropertyName("lang")]
|
||||
public string Language { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 导出的时间戳
|
||||
/// </summary>
|
||||
[JsonPropertyName("export_timestamp")]
|
||||
public long? ExportTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 导出时间
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset ExportDateTime
|
||||
{
|
||||
get => DateTimeOffsetExtension.UnsafeRelaxedFromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出的 App 名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("export_app")]
|
||||
public string ExportApp { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 导出的 App 版本
|
||||
/// </summary>
|
||||
[JsonPropertyName("export_app_version")]
|
||||
public string ExportAppVersion { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 使用的UIGF版本
|
||||
/// </summary>
|
||||
[JsonPropertyName("uigf_version")]
|
||||
public string UIGFVersion { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 时区偏移
|
||||
/// </summary>
|
||||
[JsonPropertyName("region_time_zone")]
|
||||
public int? RegionTimeZone { get; set; } = default!;
|
||||
|
||||
public static LegacyUIGFInfo From(RuntimeOptions runtimeOptions, CultureOptions cultureOptions, string uid)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Uid = uid,
|
||||
Language = cultureOptions.LanguageCode,
|
||||
ExportTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
ExportApp = SH.AppName,
|
||||
ExportAppVersion = runtimeOptions.Version.ToString(),
|
||||
UIGFVersion = LegacyUIGF.CurrentVersion,
|
||||
RegionTimeZone = PlayerUid.GetRegionTimeZoneUtcOffsetForUid(uid).Hours,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Core.Json.Annotation;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Metadata.Abstraction;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
/// <summary>
|
||||
/// UIGF物品
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Obsolete]
|
||||
internal sealed class LegacyUIGFItem : GachaLogItem, IMappingFrom<LegacyUIGFItem, GachaItem, INameQualityAccess>
|
||||
{
|
||||
/// <summary>
|
||||
/// 额外祈愿映射
|
||||
/// </summary>
|
||||
[JsonPropertyName("uigf_gacha_type")]
|
||||
[JsonEnum(JsonSerializeType.NumberString)]
|
||||
public GachaType UIGFGachaType { get; set; } = default!;
|
||||
|
||||
public static LegacyUIGFItem From(GachaItem item, INameQualityAccess nameQuality)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
GachaType = item.GachaType,
|
||||
ItemId = $"{item.ItemId:D}",
|
||||
Count = 1,
|
||||
Time = item.Time,
|
||||
Name = nameQuality.Name,
|
||||
ItemType = GetItemTypeStringByItemId(item.ItemId),
|
||||
RankType = nameQuality.Quality,
|
||||
Id = item.Id,
|
||||
UIGFGachaType = item.QueryType,
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetItemTypeStringByItemId(uint itemId)
|
||||
{
|
||||
return itemId.StringLength() switch
|
||||
{
|
||||
8U => SH.ModelInterchangeUIGFItemTypeAvatar,
|
||||
5U => SH.ModelInterchangeUIGFItemTypeWeapon,
|
||||
_ => SH.ModelInterchangeUIGFItemTypeUnknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
/// <summary>
|
||||
/// UIGF版本
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
internal enum LegacyUIGFVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// 不支持的版本
|
||||
/// </summary>
|
||||
NotSupported,
|
||||
|
||||
/// <summary>
|
||||
/// v2.2以及之前的版本
|
||||
/// </summary>
|
||||
Major2Minor2OrLower,
|
||||
|
||||
/// <summary>
|
||||
/// v2.3以及之后的版本
|
||||
/// </summary>
|
||||
Major2Minor3OrHigher,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
internal sealed class UIGF
|
||||
{
|
||||
@@ -10,7 +10,4 @@ internal sealed class UIGF
|
||||
|
||||
[JsonPropertyName("hk4e")]
|
||||
public List<UIGFEntry<Hk4eItem>>? Hk4e { get; set; }
|
||||
|
||||
[JsonPropertyName("snap_hutao_reserved")]
|
||||
public HutaoReserved? HutaoReserved { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
internal sealed class UIGFEntry<TItem>
|
||||
{
|
||||
@@ -16,4 +16,7 @@ internal sealed class UIGFEntry<TItem>
|
||||
|
||||
[JsonPropertyName("list")]
|
||||
public required List<TItem> List { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsSelected { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
namespace Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
internal sealed class UIGFInfo
|
||||
{
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
|
||||
internal sealed class HutaoReserved
|
||||
{
|
||||
public required uint Version { get; set; }
|
||||
|
||||
public List<HutaoReservedEntry<HutaoReservedAchievement>>? Achievement { get; set; }
|
||||
|
||||
public List<HutaoReservedEntry<Web.Enka.Model.AvatarInfo>>? AvatarInfo { get; set; }
|
||||
|
||||
public List<HutaoReservedEntry<HutaoReservedCultivationEntry>>? Cultivation { get; set; }
|
||||
|
||||
public List<HutaoReservedEntry<HutaoReservedSpiralAbyssEntry>>? SpiralAbyss { get; set; }
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
|
||||
internal sealed class HutaoReservedAchievement : IMappingFrom<HutaoReservedAchievement, Model.Entity.Achievement>
|
||||
{
|
||||
public required uint Id { get; set; }
|
||||
|
||||
public required uint Current { get; set; }
|
||||
|
||||
public required DateTimeOffset Time { get; set; }
|
||||
|
||||
public required AchievementStatus Status { get; set; }
|
||||
|
||||
public static HutaoReservedAchievement From(Entity.Achievement source)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = source.Id,
|
||||
Current = source.Current,
|
||||
Time = source.Time,
|
||||
Status = source.Status,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
|
||||
internal sealed class HutaoReservedCultivationEntry
|
||||
{
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint AvatarLevelFrom { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint AvatarLevelTo { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint SkillALevelFrom { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint SkillALevelTo { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint SkillELevelFrom { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint SkillELevelTo { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint SkillQLevelFrom { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint SkillQLevelTo { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint WeaponLevelFrom { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public uint WeaponLevelTo { get; set; }
|
||||
|
||||
public required CultivateType Type { get; set; }
|
||||
|
||||
public required uint Id { get; set; }
|
||||
|
||||
public required List<HutaoReservedCultivationItem> Items { get; set; }
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
|
||||
internal sealed class HutaoReservedCultivationItem : IMappingFrom<HutaoReservedCultivationItem, CultivateItem>
|
||||
{
|
||||
public required uint ItemId { get; set; }
|
||||
|
||||
public required uint Count { get; set; }
|
||||
|
||||
public required bool IsFinished { get; set; }
|
||||
|
||||
public static HutaoReservedCultivationItem From(CultivateItem item)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ItemId = item.ItemId,
|
||||
Count = item.Count,
|
||||
IsFinished = item.IsFinished,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
|
||||
internal sealed class HutaoReservedEntry<TItem>
|
||||
{
|
||||
public required string Identity { get; set; }
|
||||
|
||||
public required List<TItem> List { get; set; }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.InterChange;
|
||||
|
||||
internal sealed class HutaoReservedSpiralAbyssEntry
|
||||
{
|
||||
public required uint ScheduleId { get; set; }
|
||||
|
||||
public required Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss SpiralAbyss { get; set; }
|
||||
}
|
||||
@@ -1802,6 +1802,18 @@
|
||||
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
|
||||
<value>保存游戏路径失败</value>
|
||||
</data>
|
||||
<data name="ViewModelUIGFImportDuplicatedHk4eEntry" xml:space="preserve">
|
||||
<value>导入的 UIGF 文件中包含 UID 重复的祈愿记录项</value>
|
||||
</data>
|
||||
<data name="ViewModelUIGFImportNoHk4eEntry" xml:space="preserve">
|
||||
<value>导入的 UIGF 文件中不包含祈愿数据</value>
|
||||
</data>
|
||||
<data name="ViewModelUIGFImportNoSelectedEntry" xml:space="preserve">
|
||||
<value>请选择至少一个 UID 以导入数据</value>
|
||||
</data>
|
||||
<data name="ViewModelUIGFImportSuccess" xml:space="preserve">
|
||||
<value>导入成功</value>
|
||||
</data>
|
||||
<data name="ViewModelUserAdded" xml:space="preserve">
|
||||
<value>用户 [{0}] 添加成功</value>
|
||||
</data>
|
||||
|
||||
@@ -31,6 +31,28 @@ internal sealed partial class AvatarInfoDbBulkOperation
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public void UnsafeUpdateDbAvatarInfos(string uid, IEnumerable<EnkaAvatarInfo> webInfos)
|
||||
{
|
||||
List<EntityAvatarInfo> dbInfos = avatarInfoDbService.GetAvatarInfoListByUid(uid);
|
||||
EnsureItemsAvatarIdUnique(ref dbInfos, uid, out Dictionary<AvatarId, EntityAvatarInfo> dbInfoMap);
|
||||
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
foreach (EnkaAvatarInfo webInfo in webInfos)
|
||||
{
|
||||
if (AvatarIds.IsPlayer(webInfo.AvatarId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EntityAvatarInfo? entity = dbInfoMap.GetValueOrDefault(webInfo.AvatarId);
|
||||
AddOrUpdateAvatarInfo(entity, uid, appDbContext, webInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<List<EntityAvatarInfo>> UpdateDbAvatarInfosByShowcaseAsync(string uid, IEnumerable<EnkaAvatarInfo> webInfos, CancellationToken token)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
@@ -110,4 +110,9 @@ internal sealed partial class GachaLogDbService : IGachaLogDbService
|
||||
{
|
||||
this.Delete<GachaItem>(i => i.ArchiveId == archiveId && i.QueryType == queryType && i.Id >= endId);
|
||||
}
|
||||
|
||||
public List<GachaArchive> GetGachaArchiveList()
|
||||
{
|
||||
return this.List<GachaArchive>();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
using Snap.Hutao.Service.GachaLog.Factory;
|
||||
using Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
@@ -22,8 +21,6 @@ internal sealed partial class GachaLogService : IGachaLogService
|
||||
{
|
||||
private readonly IGachaStatisticsSlimFactory gachaStatisticsSlimFactory;
|
||||
private readonly IGachaStatisticsFactory gachaStatisticsFactory;
|
||||
private readonly IUIGFExportService gachaLogExportService;
|
||||
private readonly IUIGFImportService gachaLogImportService;
|
||||
private readonly IGachaLogDbService gachaLogDbService;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IMetadataService metadataService;
|
||||
@@ -85,17 +82,6 @@ internal sealed partial class GachaLogService : IGachaLogService
|
||||
return statistics;
|
||||
}
|
||||
|
||||
public ValueTask<LegacyUIGF> ExportToUIGFAsync(GachaArchive archive)
|
||||
{
|
||||
return gachaLogExportService.ExportAsync(context, archive);
|
||||
}
|
||||
|
||||
public async ValueTask ImportFromUIGFAsync(LegacyUIGF uigf)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(Archives);
|
||||
await gachaLogImportService.ImportAsync(context, uigf, Archives).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async ValueTask<bool> RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategyKind kind, IProgress<GachaLogFetchStatus> progress, CancellationToken token)
|
||||
{
|
||||
bool isLazy = kind switch
|
||||
|
||||
@@ -22,6 +22,8 @@ internal interface IGachaLogDbService : IAppDbService<GachaArchive>, IAppDbServi
|
||||
|
||||
GachaArchive? GetGachaArchiveByUid(string uid);
|
||||
|
||||
List<GachaArchive> GetGachaArchiveList();
|
||||
|
||||
ObservableCollection<GachaArchive> GetGachaArchiveCollection();
|
||||
|
||||
List<GachaItem> GetGachaItemListByArchiveId(Guid archiveId);
|
||||
|
||||
@@ -19,56 +19,13 @@ internal interface IGachaLogService
|
||||
|
||||
ValueTask<GachaArchive> EnsureArchiveInCollectionAsync(Guid archiveId, CancellationToken token = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// 导出为一个新的UIGF对象
|
||||
/// </summary>
|
||||
/// <param name="archive">存档</param>
|
||||
/// <returns>UIGF对象</returns>
|
||||
ValueTask<LegacyUIGF> ExportToUIGFAsync(GachaArchive archive);
|
||||
|
||||
/// <summary>
|
||||
/// 获得对应的祈愿统计
|
||||
/// </summary>
|
||||
/// <param name="archive">存档</param>
|
||||
/// <returns>祈愿统计</returns>
|
||||
ValueTask<GachaStatistics> GetStatisticsAsync(GachaArchive archive);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取简化的祈愿统计列表
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>简化的祈愿统计列表</returns>
|
||||
ValueTask<List<GachaStatisticsSlim>> GetStatisticsSlimListAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步从UIGF导入数据
|
||||
/// </summary>
|
||||
/// <param name="uigf">信息</param>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask ImportFromUIGFAsync(LegacyUIGF uigf);
|
||||
|
||||
/// <summary>
|
||||
/// 异步初始化
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>是否初始化成功</returns>
|
||||
ValueTask<bool> InitializeAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 刷新祈愿记录
|
||||
/// 切换选中的存档
|
||||
/// </summary>
|
||||
/// <param name="query">查询语句</param>
|
||||
/// <param name="strategy">刷新策略</param>
|
||||
/// <param name="progress">进度</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>验证密钥是否有效</returns>
|
||||
ValueTask<bool> RefreshGachaLogAsync(GachaLogQuery query, RefreshStrategyKind strategy, IProgress<GachaLogFetchStatus> progress, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// 删除存档
|
||||
/// </summary>
|
||||
/// <param name="archive">存档</param>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask RemoveArchiveAsync(GachaArchive archive);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录导出服务
|
||||
/// </summary>
|
||||
internal interface IUIGFExportService
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步导出存档到 UIGF
|
||||
/// </summary>
|
||||
/// <param name="context">元数据上下文</param>
|
||||
/// <param name="archive">存档</param>
|
||||
/// <returns>UIGF</returns>
|
||||
ValueTask<LegacyUIGF> ExportAsync(GachaLogServiceMetadataContext context, GachaArchive archive);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录导入服务
|
||||
/// </summary>
|
||||
internal interface IUIGFImportService
|
||||
{
|
||||
ValueTask ImportAsync(GachaLogServiceMetadataContext context, LegacyUIGF uigf, AdvancedDbCollectionView<GachaArchive> archives);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ internal sealed partial class GachaLogQueryProviderFactory : IGachaLogQueryProvi
|
||||
|
||||
public IGachaLogQueryProvider Create(RefreshOption option)
|
||||
{
|
||||
// TODO: replace with keyed services
|
||||
return option switch
|
||||
{
|
||||
RefreshOption.SToken => serviceProvider.GetRequiredService<GachaLogQuerySTokenProvider>(),
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿记录导出服务
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped, typeof(IUIGFExportService))]
|
||||
internal sealed partial class UIGFExportService : IUIGFExportService
|
||||
{
|
||||
private readonly IGachaLogDbService gachaLogDbService;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly CultureOptions cultureOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<LegacyUIGF> ExportAsync(GachaLogServiceMetadataContext context, GachaArchive archive)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
List<GachaItem> entities = gachaLogDbService.GetGachaItemListByArchiveId(archive.InnerId);
|
||||
List<LegacyUIGFItem> list = entities.SelectList(i => LegacyUIGFItem.From(i, context.GetNameQualityByItemId(i.ItemId)));
|
||||
|
||||
LegacyUIGF uigf = new()
|
||||
{
|
||||
Info = LegacyUIGFInfo.From(runtimeOptions, cultureOptions, archive.Uid),
|
||||
List = list,
|
||||
};
|
||||
|
||||
return uigf;
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
|
||||
namespace Snap.Hutao.Service.GachaLog;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped, typeof(IUIGFImportService))]
|
||||
internal sealed partial class UIGFImportService : IUIGFImportService
|
||||
{
|
||||
private readonly IGachaLogDbService gachaLogDbService;
|
||||
private readonly ILogger<UIGFImportService> logger;
|
||||
private readonly CultureOptions cultureOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public async ValueTask ImportAsync(GachaLogServiceMetadataContext context, LegacyUIGF uigf, AdvancedDbCollectionView<GachaArchive> archives)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
if (!uigf.IsCurrentVersionSupported(out LegacyUIGFVersion version))
|
||||
{
|
||||
HutaoException.InvalidOperation(SH.ServiceUIGFImportUnsupportedVersion);
|
||||
}
|
||||
|
||||
// v2.3+ supports any locale
|
||||
// v2.2 only supports matched locale
|
||||
// v2.1 only supports CHS
|
||||
if (version is LegacyUIGFVersion.Major2Minor2OrLower)
|
||||
{
|
||||
if (!cultureOptions.LanguageCodeFitsCurrentLocale(uigf.Info.Language))
|
||||
{
|
||||
string message = SH.FormatServiceGachaUIGFImportLanguageNotMatch(uigf.Info.Language, cultureOptions.LanguageCode);
|
||||
HutaoException.InvalidOperation(message);
|
||||
}
|
||||
|
||||
if (!uigf.IsMajor2Minor2OrLowerListValid(out long id))
|
||||
{
|
||||
string message = SH.FormatServiceGachaLogUIGFImportItemInvalidFormat(id);
|
||||
HutaoException.InvalidOperation(message);
|
||||
}
|
||||
}
|
||||
|
||||
if (version is LegacyUIGFVersion.Major2Minor3OrHigher)
|
||||
{
|
||||
if (!uigf.IsMajor2Minor3OrHigherListValid(out long id))
|
||||
{
|
||||
string message = SH.FormatServiceGachaLogUIGFImportItemInvalidFormat(id);
|
||||
HutaoException.InvalidOperation(message);
|
||||
}
|
||||
}
|
||||
|
||||
GachaArchiveOperation.GetOrAdd(gachaLogDbService, taskContext, uigf.Info.Uid, archives, out GachaArchive? archive);
|
||||
Guid archiveId = archive.InnerId;
|
||||
|
||||
List<GachaItem> fullItems = [];
|
||||
foreach (GachaType queryType in GachaLog.QueryTypes)
|
||||
{
|
||||
long trimId = gachaLogDbService.GetOldestGachaItemIdByArchiveIdAndQueryType(archiveId, queryType);
|
||||
logger.LogInformation("Last Id to trim with: [{Id}]", trimId);
|
||||
|
||||
List<GachaItem> currentTypedList = version switch
|
||||
{
|
||||
LegacyUIGFVersion.Major2Minor3OrHigher => uigf.List
|
||||
.Where(item => item.UIGFGachaType == queryType && item.Id < trimId)
|
||||
.OrderByDescending(item => item.Id)
|
||||
.Select(item => GachaItem.From(archiveId, item))
|
||||
.ToList(),
|
||||
LegacyUIGFVersion.Major2Minor2OrLower => uigf.List
|
||||
.Where(item => item.UIGFGachaType == queryType && item.Id < trimId)
|
||||
.OrderByDescending(item => item.Id)
|
||||
.Select(item => GachaItem.From(archiveId, item, context.GetItemId(item)))
|
||||
.ToList(),
|
||||
_ => throw HutaoException.NotSupported(),
|
||||
};
|
||||
|
||||
ThrowIfContainsInvalidItem(currentTypedList);
|
||||
fullItems.AddRange(currentTypedList);
|
||||
}
|
||||
|
||||
gachaLogDbService.AddGachaItemRange(fullItems);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
archives.MoveCurrentTo(archive);
|
||||
}
|
||||
|
||||
private static void ThrowIfContainsInvalidItem(List<GachaItem> list)
|
||||
{
|
||||
// 越早的记录手工导入的可能性越高
|
||||
// 错误率相对来说会更高
|
||||
// 因此从尾部开始查找
|
||||
if (list.LastOrDefault(item => item.ItemId is 0U) is { } item)
|
||||
{
|
||||
HutaoException.InvalidOperation(SH.FormatServiceGachaLogUIGFImportItemInvalidFormat(item.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,50 +8,22 @@ using Snap.Hutao.Service.Game.Configuration;
|
||||
|
||||
namespace Snap.Hutao.Service.Game;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏服务
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal interface IGameServiceFacade
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏内账号集合
|
||||
/// </summary>
|
||||
ObservableReorderableDbCollection<GameAccount> GameAccountCollection { get; }
|
||||
|
||||
ValueTask AttachGameAccountToUidAsync(GameAccount gameAccount, string uid);
|
||||
|
||||
ValueTask<GameAccount?> DetectGameAccountAsync(SchemeType scheme);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取游戏路径
|
||||
/// </summary>
|
||||
/// <returns>结果</returns>
|
||||
ValueTask<ValueResult<bool, string>> GetGamePathAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取多通道值
|
||||
/// </summary>
|
||||
/// <returns>多通道值</returns>
|
||||
ChannelOptions GetChannelOptions();
|
||||
|
||||
/// <summary>
|
||||
/// 游戏是否正在运行
|
||||
/// </summary>
|
||||
/// <returns>是否正在运行</returns>
|
||||
bool IsGameRunning();
|
||||
|
||||
/// <summary>
|
||||
/// 异步修改游戏账号名称
|
||||
/// </summary>
|
||||
/// <param name="gameAccount">游戏账号</param>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask ModifyGameAccountAsync(GameAccount gameAccount);
|
||||
|
||||
/// <summary>
|
||||
/// 异步尝试移除账号
|
||||
/// </summary>
|
||||
/// <param name="gameAccount">账号</param>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask RemoveGameAccountAsync(GameAccount gameAccount);
|
||||
|
||||
GameAccount? DetectCurrentGameAccount(SchemeType scheme);
|
||||
|
||||
@@ -5,5 +5,5 @@ namespace Snap.Hutao.Service.UIGF;
|
||||
|
||||
internal interface IUIGFExportService
|
||||
{
|
||||
ValueTask<bool> ExportAsync(UIGFExportOptions exportOptions, CancellationToken token);
|
||||
ValueTask ExportAsync(UIGFExportOptions exportOptions, CancellationToken token);
|
||||
}
|
||||
@@ -5,5 +5,5 @@ namespace Snap.Hutao.Service.UIGF;
|
||||
|
||||
internal interface IUIGFImportService
|
||||
{
|
||||
ValueTask<bool> ImportAsync(UIGFImportOptions importOptions, CancellationToken token);
|
||||
ValueTask ImportAsync(UIGFImportOptions importOptions, CancellationToken token);
|
||||
}
|
||||
@@ -5,5 +5,7 @@ namespace Snap.Hutao.Service.UIGF;
|
||||
|
||||
internal interface IUIGFService
|
||||
{
|
||||
ValueTask<bool> ExportAsync(UIGFExportOptions exportOptions, CancellationToken token);
|
||||
ValueTask ExportAsync(UIGFExportOptions exportOptions, CancellationToken token = default);
|
||||
|
||||
ValueTask ImportAsync(UIGFImportOptions importOptions, CancellationToken token = default);
|
||||
}
|
||||
@@ -3,14 +3,9 @@
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Service.AvatarInfo;
|
||||
using Snap.Hutao.Service.Cultivation;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
using Snap.Hutao.Service.GachaLog;
|
||||
using Snap.Hutao.Service.SpiralAbyss;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Service.UIGF;
|
||||
|
||||
@@ -23,11 +18,11 @@ internal sealed partial class UIGF40ExportService : IUIGFExportService
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public async ValueTask<bool> ExportAsync(UIGFExportOptions exportOptions, CancellationToken token)
|
||||
public async ValueTask ExportAsync(UIGFExportOptions exportOptions, CancellationToken token = default)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
Model.InterChange.UIGF uigf = new()
|
||||
Model.InterChange.GachaLog.UIGF uigf = new()
|
||||
{
|
||||
Info = new()
|
||||
{
|
||||
@@ -36,27 +31,17 @@ internal sealed partial class UIGF40ExportService : IUIGFExportService
|
||||
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
|
||||
Version = "v4.0",
|
||||
},
|
||||
HutaoReserved = new()
|
||||
{
|
||||
Version = 1,
|
||||
},
|
||||
};
|
||||
|
||||
ExportGachaArchives(uigf, exportOptions.GachaArchiveIds);
|
||||
ExportAchievementArchives(uigf.HutaoReserved, exportOptions.ReservedAchievementArchiveIds);
|
||||
ExportAvatarInfoUids(uigf.HutaoReserved, exportOptions.ReservedAvatarInfoUids);
|
||||
ExportCultivationProjects(uigf.HutaoReserved, exportOptions.ReservedCultivationProjectIds);
|
||||
ExportSpialAbysses(uigf.HutaoReserved, exportOptions.ReservedSpiralAbyssUids);
|
||||
|
||||
using (FileStream stream = File.Create(exportOptions.FilePath))
|
||||
{
|
||||
await JsonSerializer.SerializeAsync(stream, uigf, jsonOptions, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ExportGachaArchives(Model.InterChange.UIGF uigf, List<Guid> archiveIds)
|
||||
private void ExportGachaArchives(Model.InterChange.GachaLog.UIGF uigf, List<Guid> archiveIds)
|
||||
{
|
||||
if (archiveIds.Count <= 0)
|
||||
{
|
||||
@@ -83,138 +68,4 @@ internal sealed partial class UIGF40ExportService : IUIGFExportService
|
||||
|
||||
uigf.Hk4e = results;
|
||||
}
|
||||
|
||||
private void ExportAchievementArchives(HutaoReserved hutaoReserved, List<Guid> archiveIds)
|
||||
{
|
||||
if (archiveIds.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IAchievementDbService achievementDbService = serviceProvider.GetRequiredService<IAchievementDbService>();
|
||||
|
||||
List<HutaoReservedEntry<HutaoReservedAchievement>> results = [];
|
||||
foreach (Guid archiveId in archiveIds)
|
||||
{
|
||||
AchievementArchive? archive = achievementDbService.GetAchievementArchiveById(archiveId);
|
||||
ArgumentNullException.ThrowIfNull(archive);
|
||||
List<Model.Entity.Achievement> dbItems = achievementDbService.GetAchievementListByArchiveId(archiveId);
|
||||
HutaoReservedEntry<HutaoReservedAchievement> entry = new()
|
||||
{
|
||||
Identity = archive.Name,
|
||||
List = dbItems.SelectList(HutaoReservedAchievement.From),
|
||||
};
|
||||
|
||||
results.Add(entry);
|
||||
}
|
||||
|
||||
hutaoReserved.Achievement = results;
|
||||
}
|
||||
|
||||
private void ExportAvatarInfoUids(HutaoReserved hutaoReserved, List<string> uids)
|
||||
{
|
||||
if (uids.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IAvatarInfoDbService avatarInfoDbService = serviceProvider.GetRequiredService<IAvatarInfoDbService>();
|
||||
|
||||
List<HutaoReservedEntry<Web.Enka.Model.AvatarInfo>> results = [];
|
||||
foreach (string uid in uids)
|
||||
{
|
||||
List<Model.Entity.AvatarInfo>? dbItems = avatarInfoDbService.GetAvatarInfoListByUid(uid);
|
||||
HutaoReservedEntry<Web.Enka.Model.AvatarInfo> entry = new()
|
||||
{
|
||||
Identity = uid,
|
||||
List = dbItems.SelectList(item => item.Info),
|
||||
};
|
||||
|
||||
results.Add(entry);
|
||||
}
|
||||
|
||||
hutaoReserved.AvatarInfo = results;
|
||||
}
|
||||
|
||||
private void ExportCultivationProjects(HutaoReserved hutaoReserved, List<Guid> projectIds)
|
||||
{
|
||||
if (projectIds.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ICultivationDbService cultivationDbService = serviceProvider.GetRequiredService<ICultivationDbService>();
|
||||
|
||||
List<HutaoReservedEntry<HutaoReservedCultivationEntry>> results = [];
|
||||
foreach (Guid projectId in projectIds)
|
||||
{
|
||||
CultivateProject? project = cultivationDbService.GetCultivateProjectById(projectId);
|
||||
ArgumentNullException.ThrowIfNull(project);
|
||||
List<CultivateEntry> entries = cultivationDbService.GetCultivateEntryListIncludingLevelInformationByProjectId(projectId);
|
||||
|
||||
List<HutaoReservedCultivationEntry> innerResults = [];
|
||||
foreach (ref readonly CultivateEntry innerEntry in CollectionsMarshal.AsSpan(entries))
|
||||
{
|
||||
List<CultivateItem> items = cultivationDbService.GetCultivateItemListByEntryId(innerEntry.InnerId);
|
||||
|
||||
HutaoReservedCultivationEntry innerResultEntry = new()
|
||||
{
|
||||
AvatarLevelFrom = innerEntry.LevelInformation?.AvatarLevelFrom ?? 0,
|
||||
AvatarLevelTo = innerEntry.LevelInformation?.AvatarLevelTo ?? 0,
|
||||
SkillALevelFrom = innerEntry.LevelInformation?.SkillALevelFrom ?? 0,
|
||||
SkillALevelTo = innerEntry.LevelInformation?.SkillALevelTo ?? 0,
|
||||
SkillELevelFrom = innerEntry.LevelInformation?.SkillELevelFrom ?? 0,
|
||||
SkillELevelTo = innerEntry.LevelInformation?.SkillELevelTo ?? 0,
|
||||
SkillQLevelFrom = innerEntry.LevelInformation?.SkillQLevelFrom ?? 0,
|
||||
SkillQLevelTo = innerEntry.LevelInformation?.SkillQLevelTo ?? 0,
|
||||
WeaponLevelFrom = innerEntry.LevelInformation?.WeaponLevelFrom ?? 0,
|
||||
WeaponLevelTo = innerEntry.LevelInformation?.WeaponLevelTo ?? 0,
|
||||
Type = innerEntry.Type,
|
||||
Id = innerEntry.Id,
|
||||
Items = items.SelectList(HutaoReservedCultivationItem.From),
|
||||
};
|
||||
|
||||
innerResults.Add(innerResultEntry);
|
||||
}
|
||||
|
||||
HutaoReservedEntry<HutaoReservedCultivationEntry> outerEntry = new()
|
||||
{
|
||||
Identity = project.Name,
|
||||
List = innerResults,
|
||||
};
|
||||
|
||||
results.Add(outerEntry);
|
||||
}
|
||||
|
||||
hutaoReserved.Cultivation = results;
|
||||
}
|
||||
|
||||
private void ExportSpialAbysses(HutaoReserved hutaoReserved, List<string> uids)
|
||||
{
|
||||
if (uids.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ISpiralAbyssRecordDbService spiralAbyssRecordDbService = serviceProvider.GetRequiredService<ISpiralAbyssRecordDbService>();
|
||||
|
||||
List<HutaoReservedEntry<HutaoReservedSpiralAbyssEntry>> results = [];
|
||||
foreach (string uid in uids)
|
||||
{
|
||||
Dictionary<uint, SpiralAbyssEntry> dbMap = spiralAbyssRecordDbService.GetSpiralAbyssEntryMapByUid(uid);
|
||||
HutaoReservedEntry<HutaoReservedSpiralAbyssEntry> entry = new()
|
||||
{
|
||||
Identity = uid,
|
||||
List = dbMap.Select(item => new HutaoReservedSpiralAbyssEntry
|
||||
{
|
||||
ScheduleId = item.Key,
|
||||
SpiralAbyss = item.Value.SpiralAbyss,
|
||||
}).ToList(),
|
||||
};
|
||||
|
||||
results.Add(entry);
|
||||
}
|
||||
|
||||
hutaoReserved.SpiralAbyss = results;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.InterChange;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
using Snap.Hutao.Service.GachaLog;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||
|
||||
@@ -14,18 +12,13 @@ namespace Snap.Hutao.Service.UIGF;
|
||||
[Injection(InjectAs.Transient, typeof(IUIGFImportService), Key = UIGFVersion.UIGF40)]
|
||||
internal sealed partial class UIGF40ImportService : IUIGFImportService
|
||||
{
|
||||
private readonly JsonSerializerOptions jsonOptions;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public async ValueTask<bool> ImportAsync(UIGFImportOptions importOptions, CancellationToken token)
|
||||
public async ValueTask ImportAsync(UIGFImportOptions importOptions, CancellationToken token = default)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
ImportGachaArchives(importOptions.UIGF.Hk4e, importOptions.GachaArchiveUids);
|
||||
ImportAchievementArchives(importOptions.UIGF.HutaoReserved?.Achievement, importOptions.ReservedAchievementArchiveIdentities);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ImportGachaArchives(List<UIGFEntry<Hk4eItem>>? entries, HashSet<string> uids)
|
||||
@@ -70,36 +63,4 @@ internal sealed partial class UIGF40ImportService : IUIGFImportService
|
||||
gachaLogDbService.AddGachaItemRange(fullItems);
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportAchievementArchives(List<HutaoReservedEntry<HutaoReservedAchievement>>? entries, HashSet<string> identities)
|
||||
{
|
||||
if (entries.IsNullOrEmpty() || identities.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IAchievementDbService achievementDbService = serviceProvider.GetRequiredService<IAchievementDbService>();
|
||||
AchievementDbBulkOperation achievementDbBulkOperation = serviceProvider.GetRequiredService<AchievementDbBulkOperation>();
|
||||
|
||||
foreach (HutaoReservedEntry<HutaoReservedAchievement> entry in entries)
|
||||
{
|
||||
if (!identities.Contains(entry.Identity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AchievementArchive? archive = achievementDbService.GetAchievementArchiveByName(entry.Identity);
|
||||
|
||||
if (archive is null)
|
||||
{
|
||||
archive = AchievementArchive.From(entry.Identity);
|
||||
achievementDbService.AddAchievementArchive(archive);
|
||||
}
|
||||
|
||||
Guid archiveId = archive.InnerId;
|
||||
|
||||
List<Model.Entity.Achievement> achievements = entry.List.SelectList(item => Model.Entity.Achievement.From(archiveId, item));
|
||||
achievementDbBulkOperation.Overwrite(archiveId, achievements);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,4 @@ internal sealed class UIGFExportOptions
|
||||
public required string FilePath { get; set; }
|
||||
|
||||
public required List<Guid> GachaArchiveIds { get; set; }
|
||||
|
||||
public required List<Guid> ReservedAchievementArchiveIds { get; set; }
|
||||
|
||||
public required List<string> ReservedAvatarInfoUids { get; set; }
|
||||
|
||||
public required List<Guid> ReservedCultivationProjectIds { get; set; }
|
||||
|
||||
public required List<string> ReservedSpiralAbyssUids { get; set; }
|
||||
}
|
||||
@@ -5,15 +5,7 @@ namespace Snap.Hutao.Service.UIGF;
|
||||
|
||||
internal sealed class UIGFImportOptions
|
||||
{
|
||||
public required Model.InterChange.UIGF UIGF { get; set; }
|
||||
public required Model.InterChange.GachaLog.UIGF UIGF { get; set; }
|
||||
|
||||
public required HashSet<string> GachaArchiveUids { get; set; }
|
||||
|
||||
public required HashSet<string> ReservedAchievementArchiveIdentities { get; set; }
|
||||
|
||||
public required HashSet<string> ReservedAvatarInfoIdentities { get; set; }
|
||||
|
||||
public required HashSet<string> ReservedCultivationProjectIdentities { get; set; }
|
||||
|
||||
public required HashSet<string> ReservedSpiralAbyssIdentities { get; set; }
|
||||
}
|
||||
@@ -4,14 +4,20 @@
|
||||
namespace Snap.Hutao.Service.UIGF;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Transient, typeof(IUIGFService))]
|
||||
[Injection(InjectAs.Singleton, typeof(IUIGFService))]
|
||||
internal sealed partial class UIGFService : IUIGFService
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public ValueTask<bool> ExportAsync(UIGFExportOptions exportOptions, CancellationToken token)
|
||||
public ValueTask ExportAsync(UIGFExportOptions exportOptions, CancellationToken token = default)
|
||||
{
|
||||
IUIGFExportService exportService = serviceProvider.GetRequiredKeyedService<IUIGFExportService>(UIGFVersion.UIGF40);
|
||||
return exportService.ExportAsync(exportOptions, token);
|
||||
}
|
||||
|
||||
public ValueTask ImportAsync(UIGFImportOptions importOptions, CancellationToken token = default)
|
||||
{
|
||||
IUIGFImportService importService = serviceProvider.GetRequiredKeyedService<IUIGFImportService>(UIGFVersion.UIGF40);
|
||||
return importService.ImportAsync(importOptions, token);
|
||||
}
|
||||
}
|
||||
@@ -150,6 +150,7 @@
|
||||
<None Remove="Resource\WelcomeView_Background.png" />
|
||||
<None Remove="Service\Game\Automation\ScreenCapture\GameScreenCaptureDebugPreviewWindow.xaml" />
|
||||
<None Remove="stylecop.json" />
|
||||
<None Remove="UI\Xaml\View\Dialog\UIGFImportDialog.xaml" />
|
||||
<None Remove="UI\Xaml\View\Window\WebView2Window.xaml" />
|
||||
<None Remove="UI\Xaml\View\Dialog\AchievementArchiveCreateDialog.xaml" />
|
||||
<None Remove="UI\Xaml\View\Dialog\AchievementImportDialog.xaml" />
|
||||
@@ -339,6 +340,11 @@
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="UI\Xaml\View\Dialog\UIGFImportDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="UI\Xaml\Control\StandardView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -38,4 +38,13 @@ internal sealed partial class UInt32ToGradientColorConverter : DependencyValueCo
|
||||
color.B = (byte)b;
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TimestampToLocalTimeStringConverter : ValueConverter<long, string>
|
||||
{
|
||||
public override string Convert(long from)
|
||||
{
|
||||
DateTimeOffset dto = DateTimeOffset.FromUnixTimeSeconds(from).ToLocalTime();
|
||||
return $"{dto:yyyy-MM-dd HH:mm:ss}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.UI.Xaml.View.Dialog.UIGFImportDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Snap.Hutao.UI.Xaml.View.Dialog"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shmig="using:Snap.Hutao.Model.InterChange.GachaLog"
|
||||
xmlns:shuxdcs="using:Snap.Hutao.UI.Xaml.Data.Converter.Specialized"
|
||||
xmlns:shuxm="using:Snap.Hutao.UI.Xaml.Markup"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<shuxdcs:TimestampToLocalTimeStringConverter x:Key="TimestampToLocalTimeStringConverter"/>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<cwc:UniformGrid
|
||||
Grid.Row="0"
|
||||
ColumnSpacing="16"
|
||||
Columns="3"
|
||||
RowSpacing="16">
|
||||
<cwc:HeaderedContentControl Header="{shuxm:ResourceString Name=ViewDialogImportExportApp}">
|
||||
<TextBlock
|
||||
Margin="0,4,0,0"
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind UIGF.Info.ExportApp, Mode=OneWay}"/>
|
||||
</cwc:HeaderedContentControl>
|
||||
<cwc:HeaderedContentControl Header="{shuxm:ResourceString Name=ViewDialogImportExportTime}">
|
||||
<TextBlock
|
||||
Margin="0,4,0,0"
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind UIGF.Info.ExportTimestamp, Converter={StaticResource TimestampToLocalTimeStringConverter}, Mode=OneWay}"/>
|
||||
</cwc:HeaderedContentControl>
|
||||
<cwc:HeaderedContentControl Header="{shuxm:ResourceString Name=ViewDialogImportExportAppVersion}">
|
||||
<TextBlock
|
||||
Margin="0,4,0,0"
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind UIGF.Info.ExportAppVersion, Mode=OneWay}"/>
|
||||
</cwc:HeaderedContentControl>
|
||||
<cwc:HeaderedContentControl Header="{shuxm:ResourceString Name=ViewDialogImportUIGFExportUIGFVersion}">
|
||||
<TextBlock
|
||||
Margin="0,4,0,0"
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind UIGF.Info.Version, Mode=OneWay}"/>
|
||||
</cwc:HeaderedContentControl>
|
||||
|
||||
<ItemsControl ItemsSource="{x:Bind Selections}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox Content="{Binding Uid}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</cwc:UniformGrid>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
namespace Snap.Hutao.UI.Xaml.View.Dialog;
|
||||
|
||||
[DependencyProperty("UIGF", typeof(UIGF))]
|
||||
[DependencyProperty("Selections", typeof(List<UIGFUidSelection>))]
|
||||
internal sealed partial class UIGFImportDialog : ContentDialog
|
||||
{
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public UIGFImportDialog(IServiceProvider serviceProvider, UIGF uigf)
|
||||
{
|
||||
InitializeComponent();
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
|
||||
UIGF = uigf;
|
||||
Selections = uigf.Hk4e?.SelectList(item => new UIGFUidSelection(item.Uid));
|
||||
}
|
||||
|
||||
public async ValueTask<ValueResult<bool, HashSet<string>>> GetSelectedUidsAsync()
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
if (await ShowAsync() is ContentDialogResult.Primary)
|
||||
{
|
||||
HashSet<string> uids = Selections.Where(item => item.IsSelected).Select(item => item.Uid).ToHashSet();
|
||||
return new(true, uids);
|
||||
}
|
||||
|
||||
return new(false, default!);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
|
||||
namespace Snap.Hutao.UI.Xaml.View.Dialog;
|
||||
|
||||
internal sealed class UIGFUidSelection : ObservableObject
|
||||
{
|
||||
private bool isSelected = true;
|
||||
|
||||
public UIGFUidSelection(string uid)
|
||||
{
|
||||
Uid = uid;
|
||||
}
|
||||
|
||||
public bool IsSelected { get => isSelected; set => SetProperty(ref isSelected, value); }
|
||||
|
||||
public string Uid { get; set; }
|
||||
}
|
||||
@@ -114,6 +114,7 @@
|
||||
Padding="16"
|
||||
Spacing="16">
|
||||
|
||||
<!-- 关于 -->
|
||||
<Border x:Name="ScrollViwerTopPanel" cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}" Visibility="Visible">
|
||||
@@ -193,43 +194,40 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 胡桃通行证 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding Passport}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shuxm:ResourceString Name=ViewPageSettingCardHutaoPassportHeader}"/>
|
||||
<!--
|
||||
https://github.com/DGP-Studio/Snap.Hutao/issues/1072
|
||||
ItemsRepeater will behave abnormal if no direct scrollhost wrapping it
|
||||
-->
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
|
||||
<cwc:SettingsExpander
|
||||
Description="{Binding UserOptions.UserName}"
|
||||
Description="{Binding User.UserName}"
|
||||
Header="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportHeader}"
|
||||
HeaderIcon="{shuxm:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
<Button
|
||||
Command="{Binding Passport.LoginCommand}"
|
||||
Command="{Binding LoginCommand}"
|
||||
Content="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportLoginAction}"
|
||||
Style="{ThemeResource SettingButtonStyle}"
|
||||
Visibility="{Binding UserOptions.IsLoggedIn, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
|
||||
Visibility="{Binding User.IsLoggedIn, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
|
||||
<Button
|
||||
Command="{Binding Passport.RegisterCommand}"
|
||||
Command="{Binding RegisterCommand}"
|
||||
Content="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportRegisterAction}"
|
||||
Style="{ThemeResource SettingButtonStyle}"
|
||||
Visibility="{Binding UserOptions.IsLoggedIn, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
|
||||
Visibility="{Binding User.IsLoggedIn, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
|
||||
<Button
|
||||
Command="{Binding Passport.LogoutCommand}"
|
||||
Command="{Binding LogoutCommand}"
|
||||
Content="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportLogoutAction}"
|
||||
Style="{ThemeResource SettingButtonStyle}"
|
||||
Visibility="{Binding UserOptions.IsLoggedIn, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
Visibility="{Binding User.IsLoggedIn, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
<cwc:SettingsExpander.Items>
|
||||
<cwc:SettingsCard
|
||||
Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}"
|
||||
Description="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportLicensedDeveloperDescription}"
|
||||
Header="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportLicensedDeveloperHeader}"
|
||||
Visibility="{Binding UserOptions.IsLicensedDeveloper, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
Visibility="{Binding User.IsLicensedDeveloper, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Button
|
||||
Command="{Binding OpenTestPageCommand}"
|
||||
Content="TEST"
|
||||
@@ -239,10 +237,10 @@
|
||||
Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}"
|
||||
Description="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportMaintainerDescription}"
|
||||
Header="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportMaintainerHeader}"
|
||||
Visibility="{Binding UserOptions.IsMaintainer, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
<cwc:SettingsCard Description="{Binding UserOptions.GachaLogExpireAtSlim}" Header="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportGachaLogExpiredAtHeader}"/>
|
||||
Visibility="{Binding User.IsMaintainer, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
<cwc:SettingsCard Description="{Binding User.GachaLogExpireAtSlim}" Header="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportGachaLogExpiredAtHeader}"/>
|
||||
<cwc:SettingsCard
|
||||
Command="{Binding Passport.OpenRedeemWebsiteCommand}"
|
||||
Command="{Binding OpenRedeemWebsiteCommand}"
|
||||
Description="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportRedeemCodeDescription}"
|
||||
Header="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportRedeemCodeHeader}"
|
||||
IsClickEnabled="True"/>
|
||||
@@ -252,11 +250,11 @@
|
||||
Header="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportDangerZoneHeader}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
<Button
|
||||
Command="{Binding Passport.ResetPasswordCommand}"
|
||||
Command="{Binding ResetPasswordCommand}"
|
||||
Content="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportResetPasswordAction}"
|
||||
Style="{ThemeResource SettingButtonStyle}"/>
|
||||
<Button
|
||||
Command="{Binding Passport.UnregisterCommand}"
|
||||
Command="{Binding UnregisterCommand}"
|
||||
Content="{shuxm:ResourceString Name=ViewPageSettingHutaoPassportUnregisterAction}"
|
||||
Style="{ThemeResource SettingButtonStyle}"/>
|
||||
</StackPanel>
|
||||
@@ -268,7 +266,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 无感验证 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding Geetest}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shuxm:ResourceString Name=ViewPageSettingGeetestVerificationHeader}"/>
|
||||
@@ -284,7 +283,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 外观 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding Appearance}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shuxm:ResourceString Name=ViewPageSettingApperanceHeader}"/>
|
||||
@@ -357,7 +357,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 存储空间 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding Storage}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shuxm:ResourceString Name=ViewPageSettingStorageHeader}"/>
|
||||
@@ -418,7 +419,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 快捷键 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding HotKey}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shuxm:ResourceString Name=ViewPageSettingKeyShortcutHeader}"/>
|
||||
@@ -486,7 +488,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 主页 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding Home}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shuxm:ResourceString Name=ViewpageSettingHomeHeader}"/>
|
||||
@@ -537,7 +540,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 游戏 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding Game}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shuxm:ResourceString Name=ViewPageSettingGameHeader}"/>
|
||||
@@ -553,7 +557,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 祈愿记录 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding GachaLog}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shuxm:ResourceString Name=ViewPageSettingGachaLogHeader}"/>
|
||||
@@ -579,7 +584,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<!-- 危险功能 -->
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}" DataContext="{Binding DangerousFeature}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock
|
||||
|
||||
@@ -229,58 +229,6 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ImportFromUIGFJsonCommand")]
|
||||
private async Task ImportFromUIGFJsonAsync()
|
||||
{
|
||||
(bool isOk, ValueFile file) = fileSystemPickerInteraction.PickFile(
|
||||
SH.ViewModelGachaUIGFImportPickerTitile,
|
||||
[(SH.ViewModelGachaLogExportFileType, "*.json")]);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ValueResult<bool, LegacyUIGF?> result = await file.DeserializeFromJsonAsync<LegacyUIGF>(options).ConfigureAwait(false);
|
||||
if (result.TryGetValue(out LegacyUIGF? uigf))
|
||||
{
|
||||
await TryImportUIGFInternalAsync(uigf).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Error(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ExportToUIGFJsonCommand")]
|
||||
private async Task ExportToUIGFJsonAsync()
|
||||
{
|
||||
if (Archives?.CurrentItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile(
|
||||
SH.ViewModelGachaLogUIGFExportPickerTitle,
|
||||
$"{Archives.CurrentItem.Uid}.json",
|
||||
[(SH.ViewModelGachaLogExportFileType, "*.json")]);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LegacyUIGF uigf = await gachaLogService.ExportToUIGFAsync(Archives.CurrentItem).ConfigureAwait(false);
|
||||
if (await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false))
|
||||
{
|
||||
infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("RemoveArchiveCommand")]
|
||||
private async Task RemoveArchiveAsync()
|
||||
{
|
||||
@@ -347,52 +295,4 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<bool> TryImportUIGFInternalAsync(LegacyUIGF uigf)
|
||||
{
|
||||
if (!uigf.IsCurrentVersionSupported(out _))
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelGachaLogImportWarningTitle, SH.ViewModelGachaLogImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
GachaLogImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync<GachaLogImportDialog>(uigf).ConfigureAwait(false);
|
||||
if (!await importDialog.GetShouldImportAsync().ConfigureAwait(false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = await contentDialogFactory.CreateForIndeterminateProgressAsync(SH.ViewModelGachaLogImportProgress).ConfigureAwait(true);
|
||||
try
|
||||
{
|
||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
suppressCurrentItemChangedHandling = true;
|
||||
await gachaLogService.ImportFromUIGFAsync(uigf).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
suppressCurrentItemChangedHandling = false;
|
||||
await UpdateStatisticsAsync(Archives?.CurrentItem).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
// 语言不匹配/导入物品中存在无效的项
|
||||
infoBarService.Error(ex);
|
||||
return false;
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
infoBarService.Success(SH.ViewModelGachaLogImportComplete);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.UI.Xaml.View.Dialog;
|
||||
using Snap.Hutao.UI.Xaml.View.Page;
|
||||
using Snap.Hutao.Web;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using Snap.Hutao.Web.Hutao.Response;
|
||||
@@ -12,51 +14,58 @@ using Windows.System;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃通行证视图模型
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IServiceScopeFactory serviceScopeFactory;
|
||||
private readonly INavigationService navigationService;
|
||||
private readonly HutaoUserOptions hutaoUserOptions;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public HutaoUserOptions User { get => hutaoUserOptions; }
|
||||
|
||||
[Command("OpenRedeemWebsiteCommand")]
|
||||
private static async Task OpenRedeemWebsiteAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(HutaoEndpoints.Website("redeem.html").ToUri());
|
||||
}
|
||||
|
||||
[Command("OpenTestPageCommand")]
|
||||
private async Task OpenTestPageAsync()
|
||||
{
|
||||
await navigationService.NavigateAsync<TestPage>(INavigationAwaiter.Default).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("RegisterCommand")]
|
||||
private async Task RegisterAsync()
|
||||
{
|
||||
HutaoPassportRegisterDialog dialog = await contentDialogFactory.CreateInstanceAsync<HutaoPassportRegisterDialog>().ConfigureAwait(false);
|
||||
ValueResult<bool, (string UserName, string Password, string VerifyCode)> result = await dialog.GetInputAsync().ConfigureAwait(false);
|
||||
|
||||
if (result.IsOk)
|
||||
if (!result.IsOk)
|
||||
{
|
||||
(string username, string password, string verifyCode) = result.Value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(verifyCode))
|
||||
(string username, string password, string verifyCode) = result.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(verifyCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceScopeFactory.CreateScope())
|
||||
{
|
||||
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
|
||||
HutaoResponse<string> response = await hutaoPassportClient.RegisterAsync(username, password, verifyCode).ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceScopeFactory.CreateScope())
|
||||
{
|
||||
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
|
||||
|
||||
HutaoResponse<string> response = await hutaoPassportClient.RegisterAsync(username, password, verifyCode).ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
infoBarService.Information(response.GetLocalizationMessageOrMessage());
|
||||
await hutaoUserOptions.PostLoginSucceedAsync(hutaoPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
|
||||
}
|
||||
infoBarService.Information(response.GetLocalizationMessageOrMessage());
|
||||
await hutaoUserOptions.PostLoginSucceedAsync(hutaoPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,28 +76,29 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
|
||||
HutaoPassportUnregisterDialog dialog = await contentDialogFactory.CreateInstanceAsync<HutaoPassportUnregisterDialog>().ConfigureAwait(false);
|
||||
ValueResult<bool, (string UserName, string Password, string VerifyCode)> result = await dialog.GetInputAsync().ConfigureAwait(false);
|
||||
|
||||
if (result.IsOk)
|
||||
if (!result.IsOk)
|
||||
{
|
||||
(string username, string password, string verifyCode) = result.Value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(verifyCode))
|
||||
(string username, string password, string verifyCode) = result.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(verifyCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceScopeFactory.CreateScope())
|
||||
{
|
||||
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
|
||||
HutaoResponse response = await hutaoPassportClient.UnregisterAsync(username, password, verifyCode).ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
return;
|
||||
}
|
||||
infoBarService.Information(response.GetLocalizationMessageOrMessage());
|
||||
|
||||
using (IServiceScope scope = serviceScopeFactory.CreateScope())
|
||||
{
|
||||
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
|
||||
|
||||
HutaoResponse response = await hutaoPassportClient.UnregisterAsync(username, password, verifyCode).ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
infoBarService.Information(response.GetLocalizationMessageOrMessage());
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
hutaoUserOptions.PostLogoutOrUnregister();
|
||||
}
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
hutaoUserOptions.PostLogoutOrUnregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,26 +109,27 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
|
||||
HutaoPassportLoginDialog dialog = await contentDialogFactory.CreateInstanceAsync<HutaoPassportLoginDialog>().ConfigureAwait(false);
|
||||
ValueResult<bool, (string UserName, string Password)> result = await dialog.GetInputAsync().ConfigureAwait(false);
|
||||
|
||||
if (result.IsOk)
|
||||
if (!result.IsOk)
|
||||
{
|
||||
(string username, string password) = result.Value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||||
(string username, string password) = result.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceScopeFactory.CreateScope())
|
||||
{
|
||||
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
|
||||
HutaoResponse<string> response = await hutaoPassportClient.LoginAsync(username, password).ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceScopeFactory.CreateScope())
|
||||
{
|
||||
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
|
||||
|
||||
HutaoResponse<string> response = await hutaoPassportClient.LoginAsync(username, password).ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
infoBarService.Information(response.GetLocalizationMessageOrMessage());
|
||||
await hutaoUserOptions.PostLoginSucceedAsync(hutaoPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
|
||||
}
|
||||
infoBarService.Information(response.GetLocalizationMessageOrMessage());
|
||||
await hutaoUserOptions.PostLoginSucceedAsync(hutaoPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,26 +146,27 @@ internal sealed partial class HutaoPassportViewModel : Abstraction.ViewModel
|
||||
HutaoPassportResetPasswordDialog dialog = await contentDialogFactory.CreateInstanceAsync<HutaoPassportResetPasswordDialog>().ConfigureAwait(false);
|
||||
ValueResult<bool, (string UserName, string Password, string VerifyCode)> result = await dialog.GetInputAsync().ConfigureAwait(false);
|
||||
|
||||
if (result.IsOk)
|
||||
if (!result.IsOk)
|
||||
{
|
||||
(string username, string password, string verifyCode) = result.Value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(verifyCode))
|
||||
(string username, string password, string verifyCode) = result.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(verifyCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceScopeFactory.CreateScope())
|
||||
{
|
||||
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
|
||||
HutaoResponse<string> response = await hutaoPassportClient.ResetPasswordAsync(username, password, verifyCode).ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (IServiceScope scope = serviceScopeFactory.CreateScope())
|
||||
{
|
||||
HutaoPassportClient hutaoPassportClient = scope.ServiceProvider.GetRequiredService<HutaoPassportClient>();
|
||||
|
||||
HutaoResponse<string> response = await hutaoPassportClient.ResetPasswordAsync(username, password, verifyCode).ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
infoBarService.Information(response.GetLocalizationMessageOrMessage());
|
||||
await hutaoUserOptions.PostLoginSucceedAsync(hutaoPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
|
||||
}
|
||||
infoBarService.Information(response.GetLocalizationMessageOrMessage());
|
||||
await hutaoUserOptions.PostLoginSucceedAsync(hutaoPassportClient, taskContext, username, password, response.Data).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.BackgroundImage;
|
||||
using Snap.Hutao.UI.Xaml.Media.Backdrop;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingAppearanceViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly BackgroundImageOptions backgroundImageOptions;
|
||||
private readonly CultureOptions cultureOptions;
|
||||
private readonly AppOptions appOptions;
|
||||
|
||||
private NameValue<CultureInfo>? selectedCulture;
|
||||
private NameValue<BackdropType>? selectedBackdropType;
|
||||
private NameValue<ElementTheme>? selectedElementTheme;
|
||||
private NameValue<BackgroundImageType>? selectedBackgroundImageType;
|
||||
|
||||
public CultureOptions CultureOptions { get => cultureOptions; }
|
||||
|
||||
public AppOptions AppOptions { get => appOptions; }
|
||||
|
||||
public BackgroundImageOptions BackgroundImageOptions { get => backgroundImageOptions; }
|
||||
|
||||
public NameValue<CultureInfo>? SelectedCulture
|
||||
{
|
||||
get => selectedCulture ??= CultureOptions.GetCurrentCultureForSelectionOrDefault();
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedCulture, value) && value is not null)
|
||||
{
|
||||
CultureOptions.CurrentCulture = value.Value;
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NameValue<BackdropType>? SelectedBackdropType
|
||||
{
|
||||
get => selectedBackdropType ??= AppOptions.BackdropTypes.Single(t => t.Value == AppOptions.BackdropType);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedBackdropType, value) && value is not null)
|
||||
{
|
||||
AppOptions.BackdropType = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NameValue<ElementTheme>? SelectedElementTheme
|
||||
{
|
||||
get => selectedElementTheme ??= AppOptions.LazyElementThemes.Value.Single(t => t.Value == AppOptions.ElementTheme);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedElementTheme, value) && value is not null)
|
||||
{
|
||||
AppOptions.ElementTheme = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NameValue<BackgroundImageType>? SelectedBackgroundImageType
|
||||
{
|
||||
get => selectedBackgroundImageType ??= AppOptions.BackgroundImageTypes.Single(t => t.Value == AppOptions.BackgroundImageType);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedBackgroundImageType, value) && value is not null)
|
||||
{
|
||||
AppOptions.BackgroundImageType = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.UI.Xaml.View.Dialog;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingDangerousFeatureViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly IUserService userService;
|
||||
|
||||
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
|
||||
|
||||
public LaunchOptions LaunchOptions { get => launchOptions; }
|
||||
|
||||
public bool IsAllocConsoleDebugModeEnabled
|
||||
{
|
||||
get => LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, ConsoleWindowLifeTime.DebugModeEnabled);
|
||||
set
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmSetIsAllocConsoleDebugModeEnabledAsync(value).SafeForget();
|
||||
|
||||
async ValueTask ConfirmSetIsAllocConsoleDebugModeEnabledAsync(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
ReconfirmDialog dialog = await contentDialogFactory.CreateInstanceAsync<ReconfirmDialog>().ConfigureAwait(false);
|
||||
if (await dialog.ConfirmAsync(SH.ViewSettingAllocConsoleHeader).ConfigureAwait(true))
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, true);
|
||||
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, false);
|
||||
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAdvancedLaunchOptionsEnabled
|
||||
{
|
||||
get => launchOptions.IsAdvancedLaunchOptionsEnabled;
|
||||
set
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmSetIsAdvancedLaunchOptionsEnabledAsync(value).SafeForget();
|
||||
|
||||
async ValueTask ConfirmSetIsAdvancedLaunchOptionsEnabledAsync(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
ReconfirmDialog dialog = await contentDialogFactory.CreateInstanceAsync<ReconfirmDialog>().ConfigureAwait(false);
|
||||
if (await dialog.ConfirmAsync(SH.ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader).ConfigureAwait(true))
|
||||
{
|
||||
launchOptions.IsAdvancedLaunchOptionsEnabled = true;
|
||||
OnPropertyChanged(nameof(IsAdvancedLaunchOptionsEnabled));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
launchOptions.IsAdvancedLaunchOptionsEnabled = false;
|
||||
OnPropertyChanged(nameof(IsAdvancedLaunchOptionsEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Command("DeleteUsersCommand")]
|
||||
private async Task DeleteUsersAsync()
|
||||
{
|
||||
if (userService is not IUserServiceUnsafe @unsafe)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ContentDialogResult result = await contentDialogFactory
|
||||
.CreateForConfirmCancelAsync(SH.ViewDialogSettingDeleteUserDataTitle, SH.ViewDialogSettingDeleteUserDataContent)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result is ContentDialogResult.Primary)
|
||||
{
|
||||
await @unsafe.UnsafeRemoveAllUsersAsync().ConfigureAwait(false);
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,13 @@ using Windows.System;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
internal sealed partial class FolderViewModel : ObservableObject
|
||||
internal sealed partial class SettingFolderViewModel : ObservableObject
|
||||
{
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly string folder;
|
||||
private string? size;
|
||||
|
||||
public FolderViewModel(ITaskContext taskContext, string folder)
|
||||
public SettingFolderViewModel(ITaskContext taskContext, string folder)
|
||||
{
|
||||
this.taskContext = taskContext;
|
||||
this.folder = folder;
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.GachaLog;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.UIGF;
|
||||
using Snap.Hutao.UI.Xaml.View.Dialog;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingGachaLogViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IGachaLogDbService gachaLogDbService;
|
||||
private readonly JsonSerializerOptions jsonOptions;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly IUIGFService uigfService;
|
||||
private readonly AppOptions appOptions;
|
||||
|
||||
public AppOptions AppOptions { get => appOptions; }
|
||||
|
||||
[Command("ImportUIGFJsonCommand")]
|
||||
private async Task ImportUIGFJsonAsync()
|
||||
{
|
||||
(bool isOk, ValueFile file) = fileSystemPickerInteraction.PickFile(
|
||||
SH.ViewModelGachaUIGFImportPickerTitile,
|
||||
[(SH.ViewModelGachaLogExportFileType, "*.json")]);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ValueResult<bool, UIGF?> result = await file.DeserializeFromJsonAsync<UIGF>(jsonOptions).ConfigureAwait(false);
|
||||
if (!result.TryGetValue(out UIGF? uigf))
|
||||
{
|
||||
infoBarService.Error(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uigf.Hk4e.IsNullOrEmpty())
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelUIGFImportNoHk4eEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uigf.Hk4e.Select(entry => entry.Uid).ToHashSet().Count != uigf.Hk4e.Count)
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelUIGFImportDuplicatedHk4eEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
UIGFImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync<UIGFImportDialog>(uigf).ConfigureAwait(false);
|
||||
(bool isOk2, HashSet<string> uids) = await importDialog.GetSelectedUidsAsync().ConfigureAwait(false);
|
||||
if (!isOk2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (uids.IsNullOrEmpty())
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelUIGFImportNoSelectedEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
UIGFImportOptions options = new()
|
||||
{
|
||||
UIGF = uigf,
|
||||
GachaArchiveUids = uids,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await uigfService.ImportAsync(options).ConfigureAwait(false);
|
||||
infoBarService.Success(SH.ViewModelUIGFImportSuccess);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService.Error(ex, SH.ViewModelUIGFImportSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ExportUIGFJsonCommand")]
|
||||
private async Task ExportUIGFJsonAsync()
|
||||
{
|
||||
(bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile(
|
||||
SH.ViewModelGachaLogUIGFExportPickerTitle,
|
||||
$"Snap Hutao UIGF.json",
|
||||
[(SH.ViewModelGachaLogExportFileType, "*.json")]);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
LegacyUIGF uigf = await gachaLogService.ExportToUIGFAsync(Archives.CurrentItem).ConfigureAwait(false);
|
||||
if (await file.SerializeToJsonAsync(uigf, options).ConfigureAwait(false))
|
||||
{
|
||||
infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingGameViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
|
||||
[Command("DeleteGameWebCacheCommand")]
|
||||
private void DeleteGameWebCache()
|
||||
{
|
||||
string gamePath = launchOptions.GamePath;
|
||||
|
||||
if (string.IsNullOrEmpty(gamePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string cacheFilePath = GachaLogQueryWebCacheProvider.GetCacheFile(gamePath);
|
||||
string? cacheFolder = Path.GetDirectoryName(cacheFilePath);
|
||||
|
||||
if (Directory.Exists(cacheFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(cacheFolder, true);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelSettingClearWebCacheFail);
|
||||
return;
|
||||
}
|
||||
|
||||
infoBarService.Success(SH.ViewModelSettingClearWebCacheSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.FormatViewModelSettingClearWebCachePathInvalid(cacheFolder));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.UI.Xaml.View.Dialog;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingGeetestViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly AppOptions appOptions;
|
||||
|
||||
[Command("ConfigureGeetestUrlCommand")]
|
||||
private async Task ConfigureGeetestUrlAsync()
|
||||
{
|
||||
GeetestCustomUrlDialog dialog = await contentDialogFactory.CreateInstanceAsync<GeetestCustomUrlDialog>().ConfigureAwait(false);
|
||||
(bool isOk, string template) = await dialog.GetUrlAsync().ConfigureAwait(false);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
appOptions.GeetestCustomCompositeUrl = template;
|
||||
infoBarService.Success(SH.ViewModelSettingGeetestCustomUrlSucceed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingHomeViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly HomeCardOptions homeCardOptions = new();
|
||||
private readonly AppOptions appOptions;
|
||||
|
||||
private NameValue<Region>? selectedRegion;
|
||||
|
||||
public HomeCardOptions HomeCardOptions { get => homeCardOptions; }
|
||||
|
||||
public AppOptions AppOptions { get => appOptions; }
|
||||
|
||||
public NameValue<Region>? SelectedRegion
|
||||
{
|
||||
get => selectedRegion ??= AppOptions.GetCurrentRegionForSelectionOrDefault();
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedRegion, value) && value is not null)
|
||||
{
|
||||
AppOptions.Region = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.UI.Input.HotKey;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingHotKeyViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly HotKeyOptions hotKeyOptions;
|
||||
|
||||
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
|
||||
|
||||
public HotKeyOptions HotKeyOptions { get => hotKeyOptions; }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.UI.Xaml.Control;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
using System.IO;
|
||||
using Windows.System;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingStorageViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
private SettingFolderViewModel? cacheFolderView;
|
||||
private SettingFolderViewModel? dataFolderView;
|
||||
|
||||
public SettingFolderViewModel? CacheFolderView { get => cacheFolderView; set => SetProperty(ref cacheFolderView, value); }
|
||||
|
||||
public SettingFolderViewModel? DataFolderView { get => dataFolderView; set => SetProperty(ref dataFolderView, value); }
|
||||
|
||||
[Command("OpenBackgroundImageFolderCommand")]
|
||||
private async Task OpenBackgroundImageFolderAsync()
|
||||
{
|
||||
await Launcher.LaunchFolderPathAsync(runtimeOptions.GetDataFolderBackgroundFolder());
|
||||
}
|
||||
|
||||
[Command("SetDataFolderCommand")]
|
||||
private void SetDataFolder()
|
||||
{
|
||||
if (fileSystemPickerInteraction.PickFolder().TryGetValue(out string folder))
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.DataFolderPath, folder);
|
||||
infoBarService.Success(SH.ViewModelSettingSetDataFolderSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("DeleteServerCacheFolderCommand")]
|
||||
private async Task DeleteServerCacheFolderAsync()
|
||||
{
|
||||
ContentDialogResult result = await contentDialogFactory.CreateForConfirmCancelAsync(
|
||||
SH.ViewModelSettingDeleteServerCacheFolderTitle,
|
||||
SH.ViewModelSettingDeleteServerCacheFolderContent)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result is not ContentDialogResult.Primary)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string cacheFolder = runtimeOptions.GetDataFolderServerCacheFolder();
|
||||
if (Directory.Exists(cacheFolder))
|
||||
{
|
||||
Directory.Delete(cacheFolder, true);
|
||||
}
|
||||
|
||||
if (DataFolderView is not null)
|
||||
{
|
||||
await DataFolderView.SetFolderSizeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
infoBarService.Success(SH.ViewModelSettingActionComplete);
|
||||
}
|
||||
|
||||
[Command("ResetStaticResourceCommand")]
|
||||
private async Task ResetStaticResource()
|
||||
{
|
||||
ContentDialog dialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelSettingResetStaticResourceProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
StaticResource.FailAll();
|
||||
Directory.Delete(Path.Combine(runtimeOptions.LocalCache, nameof(ImageCache)), true);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
}
|
||||
|
||||
// TODO: prompt user that restart will be non-elevated
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -1,369 +1,68 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Core.Shell;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.BackgroundImage;
|
||||
using Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.UI.Input.HotKey;
|
||||
using Snap.Hutao.UI.Xaml.Control;
|
||||
using Snap.Hutao.UI.Xaml.Media.Backdrop;
|
||||
using Snap.Hutao.UI.Xaml.View.Dialog;
|
||||
using Snap.Hutao.UI.Xaml.View.Page;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Windows.System;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Setting;
|
||||
|
||||
/// <summary>
|
||||
/// 设置视图模型
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
{
|
||||
private readonly HomeCardOptions homeCardOptions = new();
|
||||
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly SettingDangerousFeatureViewModel dangerousFeatureViewModel;
|
||||
private readonly SettingAppearanceViewModel appearanceViewModel;
|
||||
private readonly HutaoPassportViewModel hutaoPassportViewModel;
|
||||
private readonly BackgroundImageOptions backgroundImageOptions;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly INavigationService navigationService;
|
||||
private readonly SettingGachaLogViewModel gachaLogViewModel;
|
||||
private readonly SettingGeetestViewModel geetestViewModel;
|
||||
private readonly SettingStorageViewModel storageViewModel;
|
||||
private readonly SettingHotKeyViewModel hotKeyViewModel;
|
||||
private readonly SettingHomeViewModel homeViewModel;
|
||||
private readonly SettingGameViewModel gameViewModel;
|
||||
private readonly IShellLinkInterop shellLinkInterop;
|
||||
private readonly HutaoUserOptions hutaoUserOptions;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly CultureOptions cultureOptions;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly HotKeyOptions hotKeyOptions;
|
||||
private readonly IUserService userService;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly AppOptions appOptions;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
private NameValue<BackdropType>? selectedBackdropType;
|
||||
private NameValue<ElementTheme>? selectedElementTheme;
|
||||
private NameValue<BackgroundImageType>? selectedBackgroundImageType;
|
||||
private NameValue<CultureInfo>? selectedCulture;
|
||||
private NameValue<Region>? selectedRegion;
|
||||
private FolderViewModel? cacheFolderView;
|
||||
private FolderViewModel? dataFolderView;
|
||||
|
||||
public AppOptions AppOptions { get => appOptions; }
|
||||
|
||||
public CultureOptions CultureOptions { get => cultureOptions; }
|
||||
|
||||
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
|
||||
|
||||
public HutaoUserOptions UserOptions { get => hutaoUserOptions; }
|
||||
|
||||
public HomeCardOptions HomeCardOptions { get => homeCardOptions; }
|
||||
|
||||
public HotKeyOptions HotKeyOptions { get => hotKeyOptions; }
|
||||
|
||||
public LaunchOptions LaunchOptions { get => launchOptions; }
|
||||
|
||||
public BackgroundImageOptions BackgroundImageOptions { get => backgroundImageOptions; }
|
||||
|
||||
public HutaoPassportViewModel Passport { get => hutaoPassportViewModel; }
|
||||
|
||||
public NameValue<BackdropType>? SelectedBackdropType
|
||||
{
|
||||
get => selectedBackdropType ??= AppOptions.BackdropTypes.Single(t => t.Value == AppOptions.BackdropType);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedBackdropType, value) && value is not null)
|
||||
{
|
||||
AppOptions.BackdropType = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public SettingGeetestViewModel Geetest { get => geetestViewModel; }
|
||||
|
||||
public NameValue<ElementTheme>? SelectedElementTheme
|
||||
{
|
||||
get => selectedElementTheme ??= AppOptions.LazyElementThemes.Value.Single(t => t.Value == AppOptions.ElementTheme);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedElementTheme, value) && value is not null)
|
||||
{
|
||||
AppOptions.ElementTheme = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public SettingAppearanceViewModel Appearance { get => appearanceViewModel; }
|
||||
|
||||
public NameValue<BackgroundImageType>? SelectedBackgroundImageType
|
||||
{
|
||||
get => selectedBackgroundImageType ??= AppOptions.BackgroundImageTypes.Single(t => t.Value == AppOptions.BackgroundImageType);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedBackgroundImageType, value) && value is not null)
|
||||
{
|
||||
AppOptions.BackgroundImageType = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public SettingStorageViewModel Storage { get => storageViewModel; }
|
||||
|
||||
public NameValue<CultureInfo>? SelectedCulture
|
||||
{
|
||||
get => selectedCulture ??= CultureOptions.GetCurrentCultureForSelectionOrDefault();
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedCulture, value) && value is not null)
|
||||
{
|
||||
CultureOptions.CurrentCulture = value.Value;
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
public SettingHotKeyViewModel HotKey { get => hotKeyViewModel; }
|
||||
|
||||
public NameValue<Region>? SelectedRegion
|
||||
{
|
||||
get => selectedRegion ??= AppOptions.GetCurrentRegionForSelectionOrDefault();
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref selectedRegion, value) && value is not null)
|
||||
{
|
||||
AppOptions.Region = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public SettingHomeViewModel Home { get => homeViewModel; }
|
||||
|
||||
public FolderViewModel? CacheFolderView { get => cacheFolderView; set => SetProperty(ref cacheFolderView, value); }
|
||||
public SettingGameViewModel Game { get => gameViewModel; }
|
||||
|
||||
public FolderViewModel? DataFolderView { get => dataFolderView; set => SetProperty(ref dataFolderView, value); }
|
||||
public SettingGachaLogViewModel GachaLog { get => gachaLogViewModel; }
|
||||
|
||||
public bool IsAllocConsoleDebugModeEnabled
|
||||
{
|
||||
get => LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, ConsoleWindowLifeTime.DebugModeEnabled);
|
||||
set
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmSetIsAllocConsoleDebugModeEnabledAsync(value).SafeForget();
|
||||
|
||||
async ValueTask ConfirmSetIsAllocConsoleDebugModeEnabledAsync(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
ReconfirmDialog dialog = await contentDialogFactory.CreateInstanceAsync<ReconfirmDialog>().ConfigureAwait(false);
|
||||
if (await dialog.ConfirmAsync(SH.ViewSettingAllocConsoleHeader).ConfigureAwait(true))
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, true);
|
||||
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, false);
|
||||
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAdvancedLaunchOptionsEnabled
|
||||
{
|
||||
get => launchOptions.IsAdvancedLaunchOptionsEnabled;
|
||||
set
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmSetIsAdvancedLaunchOptionsEnabledAsync(value).SafeForget();
|
||||
|
||||
async ValueTask ConfirmSetIsAdvancedLaunchOptionsEnabledAsync(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
ReconfirmDialog dialog = await contentDialogFactory.CreateInstanceAsync<ReconfirmDialog>().ConfigureAwait(false);
|
||||
if (await dialog.ConfirmAsync(SH.ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader).ConfigureAwait(true))
|
||||
{
|
||||
launchOptions.IsAdvancedLaunchOptionsEnabled = true;
|
||||
OnPropertyChanged(nameof(IsAdvancedLaunchOptionsEnabled));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
launchOptions.IsAdvancedLaunchOptionsEnabled = false;
|
||||
OnPropertyChanged(nameof(IsAdvancedLaunchOptionsEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
public SettingDangerousFeatureViewModel DangerousFeature { get => dangerousFeatureViewModel; }
|
||||
|
||||
protected override ValueTask<bool> InitializeOverrideAsync()
|
||||
{
|
||||
CacheFolderView = new(taskContext, runtimeOptions.LocalCache);
|
||||
DataFolderView = new(taskContext, runtimeOptions.DataFolder);
|
||||
Storage.CacheFolderView = new(taskContext, runtimeOptions.LocalCache);
|
||||
Storage.DataFolderView = new(taskContext, runtimeOptions.DataFolder);
|
||||
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
|
||||
[Command("StoreReviewCommand")]
|
||||
private static async Task StoreReviewAsync()
|
||||
protected override void UninitializeOverride()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new("ms-windows-store://review/?ProductId=9PH4NXJ2JN52"));
|
||||
}
|
||||
|
||||
[Command("UpdateCheckCommand")]
|
||||
private static async Task CheckUpdateAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new("ms-windows-store://pdp/?productid=9PH4NXJ2JN52"));
|
||||
}
|
||||
|
||||
[Command("ResetStaticResourceCommand")]
|
||||
private async Task ResetStaticResource()
|
||||
{
|
||||
ContentDialog dialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelSettingResetStaticResourceProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
StaticResource.FailAll();
|
||||
Directory.Delete(Path.Combine(runtimeOptions.LocalCache, nameof(ImageCache)), true);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
}
|
||||
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
|
||||
[Command("DeleteGameWebCacheCommand")]
|
||||
private void DeleteGameWebCache()
|
||||
{
|
||||
string gamePath = launchOptions.GamePath;
|
||||
|
||||
if (!string.IsNullOrEmpty(gamePath))
|
||||
{
|
||||
string cacheFilePath = GachaLogQueryWebCacheProvider.GetCacheFile(gamePath);
|
||||
string? cacheFolder = Path.GetDirectoryName(cacheFilePath);
|
||||
|
||||
if (Directory.Exists(cacheFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(cacheFolder, true);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelSettingClearWebCacheFail);
|
||||
return;
|
||||
}
|
||||
|
||||
infoBarService.Success(SH.ViewModelSettingClearWebCacheSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.FormatViewModelSettingClearWebCachePathInvalid(cacheFolder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Command("OpenTestPageCommand")]
|
||||
private async Task OpenTestPageAsync()
|
||||
{
|
||||
await navigationService
|
||||
.NavigateAsync<TestPage>(INavigationAwaiter.Default)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("SetDataFolderCommand")]
|
||||
private void SetDataFolder()
|
||||
{
|
||||
(bool isOk, string folder) = fileSystemPickerInteraction.PickFolder();
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.DataFolderPath, folder);
|
||||
infoBarService.Success(SH.ViewModelSettingSetDataFolderSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("DeleteServerCacheFolderCommand")]
|
||||
private async Task DeleteServerCacheFolderAsync()
|
||||
{
|
||||
ContentDialogResult result = await contentDialogFactory.CreateForConfirmCancelAsync(
|
||||
SH.ViewModelSettingDeleteServerCacheFolderTitle,
|
||||
SH.ViewModelSettingDeleteServerCacheFolderContent)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result is ContentDialogResult.Primary)
|
||||
{
|
||||
string cacheFolder = runtimeOptions.GetDataFolderServerCacheFolder();
|
||||
if (Directory.Exists(cacheFolder))
|
||||
{
|
||||
Directory.Delete(cacheFolder, true);
|
||||
}
|
||||
|
||||
if (DataFolderView is not null)
|
||||
{
|
||||
await DataFolderView.SetFolderSizeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
infoBarService.Success(SH.ViewModelSettingActionComplete);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("OpenBackgroundImageFolderCommand")]
|
||||
private async Task OpenBackgroundImageFolderAsync()
|
||||
{
|
||||
await Launcher.LaunchFolderPathAsync(runtimeOptions.GetDataFolderBackgroundFolder());
|
||||
}
|
||||
|
||||
[Command("DeleteUsersCommand")]
|
||||
private async Task DangerousDeleteUsersAsync()
|
||||
{
|
||||
if (userService is IUserServiceUnsafe @unsafe)
|
||||
{
|
||||
ContentDialogResult result = await contentDialogFactory
|
||||
.CreateForConfirmCancelAsync(SH.ViewDialogSettingDeleteUserDataTitle, SH.ViewDialogSettingDeleteUserDataContent)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result is ContentDialogResult.Primary)
|
||||
{
|
||||
await @unsafe.UnsafeRemoveAllUsersAsync().ConfigureAwait(false);
|
||||
AppInstance.Restart(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ConfigureGeetestUrlCommand")]
|
||||
private async Task ConfigureGeetestUrlAsync()
|
||||
{
|
||||
GeetestCustomUrlDialog dialog = await contentDialogFactory.CreateInstanceAsync<GeetestCustomUrlDialog>().ConfigureAwait(false);
|
||||
(bool isOk, string template) = await dialog.GetUrlAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
appOptions.GeetestCustomCompositeUrl = template;
|
||||
infoBarService.Success(SH.ViewModelSettingGeetestCustomUrlSucceed);
|
||||
}
|
||||
Passport.IsViewDisposed = true;
|
||||
Geetest.IsViewDisposed = true;
|
||||
Appearance.IsViewDisposed = true;
|
||||
Storage.IsViewDisposed = true;
|
||||
HotKey.IsViewDisposed = true;
|
||||
Home.IsViewDisposed = true;
|
||||
Game.IsViewDisposed = true;
|
||||
GachaLog.IsViewDisposed = true;
|
||||
DangerousFeature.IsViewDisposed = true;
|
||||
}
|
||||
|
||||
[Command("CreateDesktopShortcutCommand")]
|
||||
|
||||
Reference in New Issue
Block a user