mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
5 Commits
test/dialo
...
feat/gamer
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fbd648b1b | ||
|
|
107963b7ac | ||
|
|
4e89406f2f | ||
|
|
8119de3fa9 | ||
|
|
7a8c233b10 |
@@ -1,7 +1,7 @@
|
||||
name: 功能请求
|
||||
name: 功能请求
|
||||
description: 通过这个议题来向开发团队分享你的想法
|
||||
title: "[Feat]: 在这里填写一个合适的标题"
|
||||
labels: ["功能", "needs-triage", "priority:none"]
|
||||
labels: ["feature request", "needs-triage", "priority:none"]
|
||||
assignees:
|
||||
- Lightczx
|
||||
body:
|
||||
@@ -24,4 +24,4 @@ body:
|
||||
label: 想要实现或优化的功能
|
||||
description: 详细的描述一下你想要的功能,描述的越具体,采纳的可能性越高
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Feature Request [English Form]
|
||||
description: Tell us about your thought
|
||||
title: "[Feat]: Place your title here"
|
||||
labels: ["功能", "needs-triage", "priority:none"]
|
||||
labels: ["feature request", "needs-triage", "priority:none"]
|
||||
assignees:
|
||||
- Lightczx
|
||||
body:
|
||||
@@ -22,6 +22,6 @@ body:
|
||||
id: req
|
||||
attributes:
|
||||
label: Detail of the Feature
|
||||
description: Descripbe the feaure in detail. The more detailed and convincing the desciprtion the more likyly feature will be accepted.
|
||||
description: Descripbe the feaure in detail. The more detailed and convincing the desciprtion the more likyly feature will be accepted.
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
@@ -7,23 +7,45 @@ using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
[SuppressMessage("", "CA1001")]
|
||||
[DependencyProperty("MilliSecondsDelay", typeof(int))]
|
||||
internal sealed partial class InfoBarDelayCloseBehavior : BehaviorBase<InfoBar>
|
||||
{
|
||||
private readonly CancellationTokenSource closeTokenSource = new();
|
||||
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
DelayCoreAsync().SafeForget();
|
||||
AssociatedObject.Closed += OnInfoBarClosed;
|
||||
if (MilliSecondsDelay > 0)
|
||||
{
|
||||
DelayCoreAsync().SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask DelayCoreAsync()
|
||||
{
|
||||
if (MilliSecondsDelay > 0)
|
||||
try
|
||||
{
|
||||
await Delay.FromMilliSeconds(MilliSecondsDelay).ConfigureAwait(true);
|
||||
if (AssociatedObject is not null)
|
||||
{
|
||||
AssociatedObject.IsOpen = false;
|
||||
}
|
||||
await Task.Delay(MilliSecondsDelay, closeTokenSource.Token).ConfigureAwait(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (AssociatedObject is not null)
|
||||
{
|
||||
AssociatedObject.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInfoBarClosed(InfoBar infoBar, InfoBarClosedEventArgs args)
|
||||
{
|
||||
if (args.Reason is InfoBarCloseReason.CloseButton)
|
||||
{
|
||||
closeTokenSource.Cancel();
|
||||
}
|
||||
|
||||
AssociatedObject.Closed -= OnInfoBarClosed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
namespace Snap.Hutao.Control.Selector;
|
||||
|
||||
internal sealed class InfoBarTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate ActionButtonEnabled { get; set; } = default!;
|
||||
|
||||
public DataTemplate ActionButtonDisabled { get; set; } = default!;
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
if (item is InfoBarOptions { ActionButtonContent: { }, ActionButtonCommand: { } })
|
||||
{
|
||||
return ActionButtonEnabled;
|
||||
}
|
||||
|
||||
return ActionButtonDisabled;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
|
||||
<shmmc:AvatarCardConverter x:Key="AvatarCardConverter"/>
|
||||
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
|
||||
<shmmc:AvatarIconCircleConverter x:Key="AvatarIconCircleConverter"/>
|
||||
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
|
||||
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
|
||||
<shmmc:DescriptionsParametersDescriptor x:Key="DescParamDescriptor"/>
|
||||
|
||||
654
src/Snap.Hutao/Snap.Hutao/Migrations/20240613144942_UserGameRoleProfilePicture.Designer.cs
generated
Normal file
654
src/Snap.Hutao/Snap.Hutao/Migrations/20240613144942_UserGameRoleProfilePicture.Designer.cs
generated
Normal file
@@ -0,0 +1,654 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20240613144942_UserGameRoleProfilePicture")]
|
||||
partial class UserGameRoleProfilePicture
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Current")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("CalculatorRefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("GameRecordRefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Info")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("ShowcaseRefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("cultivate_entries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("AvatarLevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("AvatarLevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("EntryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("SkillALevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillALevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillELevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillELevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillQLevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SkillQLevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("WeaponLevelFrom")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("WeaponLevelTo")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("EntryId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("cultivate_entry_level_informations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Count")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("EntryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsFinished")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("EntryId");
|
||||
|
||||
b.ToTable("cultivate_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachedUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("cultivate_projects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DailyNote")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DailyTaskNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DailyTaskNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ExpeditionNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HomeCoinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("HomeCoinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("RefreshTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("ResinNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResinNotifyThreshold")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotify")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TransformerNotifySuppressed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ArchiveId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("GachaType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QueryType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Time")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AttachUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("MihoyoSDK")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("Count")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AppendPropIdList")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MainPropId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_reliquaries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PromoteLevel")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_weapons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpireTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("object_cache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("ScheduleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SpiralAbyss")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("spiral_abysses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Aid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CookieToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("CookieTokenLastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Fingerprint")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsOversea")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsSelected")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LToken")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("Ltoken");
|
||||
|
||||
b.Property<string>("Mid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredUid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SToken")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("Stoken");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.UserGameRoleProfilePicture", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("AvatarId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("CostumeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("LastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("ProfilePictureId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("profile_pictures");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
|
||||
.WithOne("LevelInformation")
|
||||
.HasForeignKey("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", "EntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
|
||||
.WithMany()
|
||||
.HasForeignKey("EntryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
|
||||
.WithMany()
|
||||
.HasForeignKey("ArchiveId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Archive");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
|
||||
{
|
||||
b.Navigation("LevelInformation");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Snap.Hutao.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class UserGameRoleProfilePicture : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "profile_pictures",
|
||||
columns: table => new
|
||||
{
|
||||
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
Uid = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ProfilePictureId = table.Column<uint>(type: "INTEGER", nullable: false),
|
||||
AvatarId = table.Column<uint>(type: "INTEGER", nullable: false),
|
||||
CostumeId = table.Column<uint>(type: "INTEGER", nullable: false),
|
||||
LastUpdateTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_profile_pictures", x => x.InnerId);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "profile_pictures");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
@@ -515,6 +515,33 @@ namespace Snap.Hutao.Migrations
|
||||
b.ToTable("users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.UserGameRoleProfilePicture", b =>
|
||||
{
|
||||
b.Property<Guid>("InnerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("AvatarId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("CostumeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("LastUpdateTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("ProfilePictureId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Uid")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("profile_pictures");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
{
|
||||
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
|
||||
|
||||
@@ -65,6 +65,8 @@ internal sealed class AppDbContext : DbContext
|
||||
|
||||
public DbSet<SpiralAbyssEntry> SpiralAbysses { get; set; } = default!;
|
||||
|
||||
public DbSet<UserGameRoleProfilePicture> UserGameRoleProfilePictures { get; set; } = default!;
|
||||
|
||||
public static AppDbContext Create(IServiceProvider serviceProvider, string sqlConnectionString)
|
||||
{
|
||||
DbContextOptions<AppDbContext> options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Snap.Hutao.Model.Entity;
|
||||
|
||||
[Table("profile_pictures")]
|
||||
[SuppressMessage("", "SH002")]
|
||||
internal sealed class UserGameRoleProfilePicture : IMappingFrom<UserGameRoleProfilePicture, PlayerUid, ProfilePicture>
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid InnerId { get; set; }
|
||||
|
||||
public string Uid { get; set; } = default!;
|
||||
|
||||
public uint ProfilePictureId { get; set; }
|
||||
|
||||
public uint AvatarId { get; set; }
|
||||
|
||||
public uint CostumeId { get; set; }
|
||||
|
||||
public DateTimeOffset LastUpdateTime { get; set; }
|
||||
|
||||
public static UserGameRoleProfilePicture From(PlayerUid uid, ProfilePicture profilePicture)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Uid = uid.ToString(),
|
||||
ProfilePictureId = profilePicture.ProfilePictureId,
|
||||
AvatarId = profilePicture.AvatarId,
|
||||
CostumeId = profilePicture.CostumeId,
|
||||
LastUpdateTime = DateTimeOffset.Now,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,8 @@ internal sealed class ProfilePicture
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
public AvatarId AvatarId { get; set; }
|
||||
|
||||
public CostumeId CostumeId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家头像转换器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class AvatarIconCircleConverter : ValueConverter<string, Uri>
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称转Uri
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns>链接</returns>
|
||||
public static Uri IconNameToUri(string name)
|
||||
{
|
||||
return Web.HutaoEndpoints.StaticRaw("AvatarIconCircle", $"{name}.png").ToUri();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Uri Convert(string from)
|
||||
{
|
||||
return IconNameToUri(from);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
|
||||
internal interface IMetadataDictionaryAvatarIdProfilePictureSource
|
||||
{
|
||||
public Dictionary<AvatarId, Model.Metadata.Avatar.ProfilePicture> AvatarIdProfilePictureMap { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
|
||||
internal interface IMetadataDictionaryCostumeIdProfilePictureSource
|
||||
{
|
||||
public Dictionary<CostumeId, Model.Metadata.Avatar.ProfilePicture> CostumeIdProfilePictureMap { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
|
||||
internal interface IMetadataDictionaryIdProfilePictureSource
|
||||
{
|
||||
public Dictionary<ProfilePictureId, Model.Metadata.Avatar.ProfilePicture> IdProfilePictureMap { get; set; }
|
||||
}
|
||||
@@ -66,6 +66,21 @@ internal static class MetadataServiceContextExtension
|
||||
dictionaryIdMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (context is IMetadataDictionaryIdProfilePictureSource dictionaryIdProfilePictureSource)
|
||||
{
|
||||
dictionaryIdProfilePictureSource.IdProfilePictureMap = await metadataService.GetIdToProfilePictureMapAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (context is IMetadataDictionaryAvatarIdProfilePictureSource dictionaryAvatarIdProfilePictureSource)
|
||||
{
|
||||
dictionaryAvatarIdProfilePictureSource.AvatarIdProfilePictureMap = await metadataService.GetAvatarIdToProfilePictureMapAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (context is IMetadataDictionaryCostumeIdProfilePictureSource dictionaryCostumeIdProfilePictureSource)
|
||||
{
|
||||
dictionaryCostumeIdProfilePictureSource.CostumeIdProfilePictureMap = await metadataService.GetCostumeIdToProfilePictureMapAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (context is IMetadataDictionaryIdReliquarySource dictionaryIdReliquarySource)
|
||||
{
|
||||
dictionaryIdReliquarySource.IdReliquaryMap = await metadataService.GetIdToReliquaryMapAsync(token).ConfigureAwait(false);
|
||||
|
||||
@@ -16,6 +16,7 @@ internal static class MetadataFileNames
|
||||
public const string FileNameMaterial = "Material";
|
||||
public const string FileNameMonster = "Monster";
|
||||
public const string FileNameMonsterCurve = "MonsterCurve";
|
||||
public const string FileNameProfilePicture = "ProfilePicture";
|
||||
public const string FileNameReliquary = "Reliquary";
|
||||
public const string FileNameReliquaryAffixWeight = "ReliquaryAffixWeight";
|
||||
public const string FileNameReliquaryMainAffix = "ReliquaryMainAffix";
|
||||
|
||||
@@ -71,6 +71,21 @@ internal static class MetadataServiceDictionaryExtension
|
||||
return metadataService.FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<ProfilePictureId, ProfilePicture>> GetIdToProfilePictureMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<ProfilePictureId, ProfilePicture>(FileNameProfilePicture, p => p.Id, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<AvatarId, ProfilePicture>> GetAvatarIdToProfilePictureMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<AvatarId, ProfilePicture>(FileNameProfilePicture, p => p.AvatarId, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<CostumeId, ProfilePicture>> GetCostumeIdToProfilePictureMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<CostumeId, ProfilePicture>(FileNameProfilePicture, p => p.CostumeId, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<ReliquaryId, Reliquary>> GetIdToReliquaryMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync(FileNameReliquary, (List<Reliquary> list) => list.SelectMany(r => r.Ids, (r, i) => (Index: i, Reliquary: r)), token);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
internal interface IUserGameRoleDbService : IAppDbService<UserGameRoleProfilePicture>
|
||||
{
|
||||
ValueTask<bool> ContainsUidAsync(string uid, CancellationToken token = default);
|
||||
|
||||
ValueTask<UserGameRoleProfilePicture> GetUserGameRoleProfilePictureByUidAsync(string uid, CancellationToken token = default);
|
||||
|
||||
ValueTask UpdateUserGameRoleProfilePictureAsync(UserGameRoleProfilePicture profilePicture, CancellationToken token = default);
|
||||
|
||||
ValueTask DeleteUserGameRoleProfilePictureByUidAsync(string uid, CancellationToken token = default);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
internal interface IUserInitializationService
|
||||
@@ -8,4 +10,6 @@ internal interface IUserInitializationService
|
||||
ValueTask<ViewModel.User.User?> CreateUserFromInputCookieOrDefaultAsync(InputCookie inputCookie, CancellationToken token = default(CancellationToken));
|
||||
|
||||
ValueTask<ViewModel.User.User> ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default(CancellationToken));
|
||||
|
||||
ValueTask RefreshUserGameRolesProfilePictureAsync(UserGameRole userGameRole, CancellationToken token = default);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
internal interface IUserMetadataContext : IMetadataContext,
|
||||
IMetadataDictionaryIdProfilePictureSource,
|
||||
IMetadataDictionaryAvatarIdProfilePictureSource,
|
||||
IMetadataDictionaryCostumeIdProfilePictureSource;
|
||||
@@ -53,4 +53,6 @@ internal interface IUserService
|
||||
/// <param name="user">待移除的用户</param>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask RemoveUserAsync(BindingUser user);
|
||||
|
||||
ValueTask RefreshUserGameRoleProfilePictureAsync(UserGameRole userGameRole);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IUserGameRoleDbService))]
|
||||
internal sealed partial class UserGameRoleDbService : IUserGameRoleDbService
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public IServiceProvider ServiceProvider { get => serviceProvider; }
|
||||
|
||||
public ValueTask<bool> ContainsUidAsync(string uid, CancellationToken token = default)
|
||||
{
|
||||
return this.QueryAsync(query => query.AnyAsync(n => n.Uid == uid));
|
||||
}
|
||||
|
||||
public ValueTask<UserGameRoleProfilePicture> GetUserGameRoleProfilePictureByUidAsync(string uid, CancellationToken token = default)
|
||||
{
|
||||
return this.QueryAsync(query => query.FirstAsync(n => n.Uid == uid));
|
||||
}
|
||||
|
||||
public async ValueTask UpdateUserGameRoleProfilePictureAsync(UserGameRoleProfilePicture profilePicture, CancellationToken token = default)
|
||||
{
|
||||
await this.UpdateAsync(profilePicture, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async ValueTask DeleteUserGameRoleProfilePictureByUidAsync(string uid, CancellationToken token = default)
|
||||
{
|
||||
await this.DeleteAsync(profilePicture => profilePicture.Uid == uid, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,18 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Entity.Extension;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
using Snap.Hutao.Web.Enka;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Passport;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using MetadataProfilePicture = Snap.Hutao.Model.Metadata.Avatar.ProfilePicture;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
@@ -16,6 +22,7 @@ namespace Snap.Hutao.Service.User;
|
||||
internal sealed partial class UserInitializationService : IUserInitializationService
|
||||
{
|
||||
private readonly IUserFingerprintService userFingerprintService;
|
||||
private readonly IUserGameRoleDbService userGameRoleDbService;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public async ValueTask<ViewModel.User.User> ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default)
|
||||
@@ -55,6 +62,30 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask RefreshUserGameRolesProfilePictureAsync(UserGameRole userGameRole, CancellationToken token = default)
|
||||
{
|
||||
EnkaResponse? enkaResponse;
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
EnkaClient enkaClient = scope.ServiceProvider
|
||||
.GetRequiredService<EnkaClient>();
|
||||
|
||||
enkaResponse = await enkaClient
|
||||
.GetForwardPlayerInfoAsync(userGameRole, token)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (enkaResponse is { PlayerInfo: { } playerInfo })
|
||||
{
|
||||
UserGameRoleProfilePicture profilePicture = UserGameRoleProfilePicture.From(userGameRole, playerInfo.ProfilePicture);
|
||||
|
||||
await userGameRoleDbService.DeleteUserGameRoleProfilePictureByUidAsync(userGameRole.GameUid, token).ConfigureAwait(false);
|
||||
await userGameRoleDbService.UpdateUserGameRoleProfilePictureAsync(profilePicture, token).ConfigureAwait(false);
|
||||
|
||||
await SetUserGameRolesProfilePictureCoreAsync(userGameRole, profilePicture, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<bool> InitializeUserAsync(ViewModel.User.User user, CancellationToken token = default)
|
||||
{
|
||||
if (user.IsInitialized)
|
||||
@@ -89,6 +120,8 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
|
||||
return false;
|
||||
}
|
||||
|
||||
TrySetUserUserGameRolesProfilePictureAsync(user, token).SafeForget();
|
||||
|
||||
await userFingerprintService.TryInitializeAsync(user, token).ConfigureAwait(false);
|
||||
|
||||
return user.IsInitialized = true;
|
||||
@@ -216,4 +249,69 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask TrySetUserUserGameRolesProfilePictureAsync(ViewModel.User.User user, CancellationToken token = default)
|
||||
{
|
||||
foreach (UserGameRole userGameRole in user.UserGameRoles)
|
||||
{
|
||||
if (await userGameRoleDbService.ContainsUidAsync(userGameRole.GameUid, token).ConfigureAwait(false))
|
||||
{
|
||||
UserGameRoleProfilePicture savedProfilePicture = await userGameRoleDbService
|
||||
.GetUserGameRoleProfilePictureByUidAsync(userGameRole.GameUid, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (await SetUserGameRolesProfilePictureCoreAsync(userGameRole, savedProfilePicture, token).ConfigureAwait(false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await RefreshUserGameRolesProfilePictureAsync(userGameRole, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<bool> SetUserGameRolesProfilePictureCoreAsync(UserGameRole userGameRole, UserGameRoleProfilePicture profilePicture, CancellationToken token = default)
|
||||
{
|
||||
if (profilePicture.LastUpdateTime.AddDays(15) < DateTimeOffset.Now)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UserMetadataContext context;
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
IMetadataService metadataService = scope.ServiceProvider
|
||||
.GetRequiredService<IMetadataService>();
|
||||
|
||||
if (!await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context = await scope.ServiceProvider
|
||||
.GetRequiredService<IMetadataService>()
|
||||
.GetContextAsync<UserMetadataContext>(token)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (context.IdProfilePictureMap.TryGetValue(profilePicture.ProfilePictureId, out MetadataProfilePicture? metadataProfilePicture))
|
||||
{
|
||||
userGameRole.ProfilePictureIcon = metadataProfilePicture.Icon;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.CostumeIdProfilePictureMap.TryGetValue(profilePicture.CostumeId, out metadataProfilePicture))
|
||||
{
|
||||
userGameRole.ProfilePictureIcon = metadataProfilePicture.Icon;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.AvatarIdProfilePictureMap.TryGetValue(profilePicture.AvatarId, out metadataProfilePicture))
|
||||
{
|
||||
userGameRole.ProfilePictureIcon = metadataProfilePicture.Icon;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
internal class UserMetadataContext : IUserMetadataContext
|
||||
{
|
||||
public Dictionary<ProfilePictureId, ProfilePicture> IdProfilePictureMap { get; set; } = default!;
|
||||
|
||||
public Dictionary<AvatarId, ProfilePicture> AvatarIdProfilePictureMap { get; set; } = default!;
|
||||
|
||||
public Dictionary<CostumeId, ProfilePicture> CostumeIdProfilePictureMap { get; set; } = default!;
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace Snap.Hutao.Service.User;
|
||||
[Injection(InjectAs.Singleton, typeof(IUserService))]
|
||||
internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
||||
{
|
||||
private readonly IUserInitializationService userInitializationService;
|
||||
private readonly IUserCollectionService userCollectionService;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IUserDbService userDbService;
|
||||
@@ -121,4 +122,9 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask RefreshUserGameRoleProfilePictureAsync(UserGameRole userGameRole)
|
||||
{
|
||||
await userInitializationService.RefreshUserGameRolesProfilePictureAsync(userGameRole).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -314,8 +314,8 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageReference Include="Google.OrTools" Version="9.10.4067" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shcs="using:Snap.Hutao.Control.Selector"
|
||||
xmlns:shsn="using:Snap.Hutao.Service.Notification"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -63,25 +64,38 @@
|
||||
<DataTemplate x:Key="InfoBarTemplate" x:DataType="shsn:InfoBarOptions">
|
||||
<InfoBar
|
||||
Title="{Binding Title}"
|
||||
Closed="OnInfoBarClosed"
|
||||
Content="{Binding Content}"
|
||||
IsOpen="True"
|
||||
Message="{Binding Message}"
|
||||
Severity="{Binding Severity}">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InfoBarDelayCloseBehavior MilliSecondsDelay="{Binding MilliSecondsDelay}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
</InfoBar>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="InfoBarWithActionButtonTemplate" x:DataType="shsn:InfoBarOptions">
|
||||
<InfoBar
|
||||
Title="{Binding Title}"
|
||||
Closed="OnInfoBarClosed"
|
||||
Content="{Binding Content}"
|
||||
IsOpen="True"
|
||||
Message="{Binding Message}"
|
||||
Closed="OnInfoBarClosed"
|
||||
Severity="{Binding Severity}">
|
||||
<InfoBar.Transitions>
|
||||
<AddDeleteThemeTransition/>
|
||||
</InfoBar.Transitions>
|
||||
<InfoBar.ActionButton>
|
||||
<Button
|
||||
Command="{Binding ActionButtonCommand}"
|
||||
Content="{Binding ActionButtonContent}"
|
||||
Visibility="{Binding ActionButtonContent, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
|
||||
<Button Command="{Binding ActionButtonCommand}" Content="{Binding ActionButtonContent}"/>
|
||||
</InfoBar.ActionButton>
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InfoBarDelayCloseBehavior MilliSecondsDelay="{Binding MilliSecondsDelay}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
</InfoBar>
|
||||
</DataTemplate>
|
||||
|
||||
<shcs:InfoBarTemplateSelector
|
||||
x:Key="InfoBarTemplateSelector"
|
||||
ActionButtonDisabled="{StaticResource InfoBarTemplate}"
|
||||
ActionButtonEnabled="{StaticResource InfoBarWithActionButtonTemplate}"/>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
@@ -91,9 +105,13 @@
|
||||
Margin="32,48,32,32"
|
||||
VerticalAlignment="Bottom"
|
||||
ItemContainerTransitions="{StaticResource RepositionThemeTransitions}"
|
||||
ItemTemplate="{StaticResource InfoBarTemplate}"
|
||||
ItemTemplateSelector="{StaticResource InfoBarTemplateSelector}"
|
||||
ItemsSource="{x:Bind InfoBars}"
|
||||
Visibility="{x:Bind VisibilityButton.IsChecked, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}"/>
|
||||
Visibility="{x:Bind VisibilityButton.IsChecked, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<ItemsControl.Transitions>
|
||||
<AddDeleteThemeTransition/>
|
||||
</ItemsControl.Transitions>
|
||||
</ItemsControl>
|
||||
|
||||
<Border
|
||||
Margin="16"
|
||||
|
||||
@@ -19,14 +19,73 @@
|
||||
<shc:BindingProxy x:Key="ViewModelBindingProxy" DataContext="{Binding}"/>
|
||||
|
||||
<DataTemplate x:Key="UserGameRoleTemplate">
|
||||
<StackPanel Padding="0,6">
|
||||
<TextBlock Text="{Binding Nickname}"/>
|
||||
<TextBlock
|
||||
Margin="0,2,0,0"
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Description}"/>
|
||||
</StackPanel>
|
||||
<Grid Padding="0,12" Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<PersonPicture
|
||||
Grid.Column="0"
|
||||
Height="32"
|
||||
Margin="2,0"
|
||||
HorizontalAlignment="Left"
|
||||
ProfilePicture="{Binding ProfilePictureIcon, Converter={StaticResource AvatarIconCircleConverter}}"/>
|
||||
<Button
|
||||
Grid.Column="0"
|
||||
Width="32"
|
||||
Height="32"
|
||||
Margin="2,0"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Left"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding DataContext.RefreshUserGameRoleProfilePictureCommand, Source={StaticResource ViewModelBindingProxy}}"
|
||||
CommandParameter="{Binding}"
|
||||
CornerRadius="8">
|
||||
<Button.Resources>
|
||||
<Storyboard x:Key="ShowRefreshIcon">
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RefreshIcon" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0">
|
||||
<DiscreteObjectKeyFrame.Value>
|
||||
<Visibility>Visible</Visibility>
|
||||
</DiscreteObjectKeyFrame.Value>
|
||||
</DiscreteObjectKeyFrame>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<Storyboard x:Key="HideRefreshIcon">
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RefreshIcon" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0">
|
||||
<DiscreteObjectKeyFrame.Value>
|
||||
<Visibility>Collapsed</Visibility>
|
||||
</DiscreteObjectKeyFrame.Value>
|
||||
</DiscreteObjectKeyFrame>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</Button.Resources>
|
||||
<mxi:Interaction.Behaviors>
|
||||
<mxic:EventTriggerBehavior EventName="PointerEntered">
|
||||
<mxim:ControlStoryboardAction Storyboard="{StaticResource ShowRefreshIcon}"/>
|
||||
</mxic:EventTriggerBehavior>
|
||||
<mxic:EventTriggerBehavior EventName="PointerExited">
|
||||
<mxim:ControlStoryboardAction Storyboard="{StaticResource HideRefreshIcon}"/>
|
||||
</mxic:EventTriggerBehavior>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<FontIcon
|
||||
x:Name="RefreshIcon"
|
||||
FontSize="12"
|
||||
Glyph=""
|
||||
Visibility="Collapsed"/>
|
||||
</Button>
|
||||
<StackPanel Grid.Column="1" Margin="12,0">
|
||||
<TextBlock Text="{Binding Nickname}"/>
|
||||
<TextBlock
|
||||
Margin="0,2,0,0"
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Description}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="UserTemplate">
|
||||
|
||||
@@ -17,6 +17,7 @@ using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.View.Page;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Passport;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Text;
|
||||
using EntityUser = Snap.Hutao.Model.Entity.User;
|
||||
@@ -297,4 +298,10 @@ internal sealed partial class UserViewModel : ObservableObject
|
||||
FlyoutBase.ShowAttachedFlyout(appBarButton);
|
||||
infoBarService.Warning(message);
|
||||
}
|
||||
|
||||
[Command("RefreshUserGameRoleProfilePictureCommand")]
|
||||
private void RefreshUserGameRoleProfilePicture(UserGameRole userGameRole)
|
||||
{
|
||||
userService.RefreshUserGameRoleProfilePictureAsync(userGameRole).SafeForget();
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,23 @@ namespace Snap.Hutao.Web.Enka;
|
||||
internal sealed partial class EnkaClient
|
||||
{
|
||||
private const string EnkaAPI = "https://enka.network/api/uid/{0}";
|
||||
private const string EnkaInfoAPI = "https://enka.network/api/uid/{0}?info";
|
||||
|
||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
public ValueTask<EnkaResponse?> GetForwardPlayerInfoAsync(in PlayerUid playerUid, CancellationToken token = default)
|
||||
{
|
||||
// TODO
|
||||
return TryGetEnkaResponseCoreAsync($"https://enka-api.hut.ao/{playerUid}?info", token);
|
||||
}
|
||||
|
||||
public ValueTask<EnkaResponse?> GetPlayerInfoAsync(in PlayerUid playerUid, CancellationToken token = default)
|
||||
{
|
||||
return TryGetEnkaResponseCoreAsync(string.Format(CultureInfo.CurrentCulture, EnkaInfoAPI, playerUid), token);
|
||||
}
|
||||
|
||||
public ValueTask<EnkaResponse?> GetForwardDataAsync(in PlayerUid playerUid, CancellationToken token = default)
|
||||
{
|
||||
return TryGetEnkaResponseCoreAsync(HutaoEndpoints.Enka(playerUid), token);
|
||||
|
||||
@@ -12,5 +12,11 @@ namespace Snap.Hutao.Web.Enka.Model;
|
||||
internal sealed class ProfilePicture
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public ProfilePictureId Id { get; set; }
|
||||
public ProfilePictureId ProfilePictureId { get; set; }
|
||||
|
||||
[JsonPropertyName("avatarId")]
|
||||
public AvatarId AvatarId { get; set; }
|
||||
|
||||
[JsonPropertyName("costumeId")]
|
||||
public CostumeId CostumeId { get; set; }
|
||||
}
|
||||
@@ -57,6 +57,8 @@ internal sealed class UserGameRole
|
||||
[JsonPropertyName("is_official")]
|
||||
public bool IsOfficial { get; set; } = default!;
|
||||
|
||||
public string ProfilePictureIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家服务器与等级简述
|
||||
/// </summary>
|
||||
|
||||
@@ -212,6 +212,11 @@ internal static class HutaoEndpoints
|
||||
return $"{ApiSnapGenshinEnka}/{uid}";
|
||||
}
|
||||
|
||||
public static string EnkaPlayerInfo(in PlayerUid uid)
|
||||
{
|
||||
return $"{ApiSnapGenshinEnka}/{uid}/info";
|
||||
}
|
||||
|
||||
public const string Ip = $"{ApiSnapGenshin}/ip";
|
||||
|
||||
#region Metadata
|
||||
@@ -292,4 +297,5 @@ internal static class HutaoEndpoints
|
||||
private const string ApiSnapGenshinStaticZip = $"{ApiSnapGenshin}/static/zip";
|
||||
private const string ApiSnapGenshinEnka = $"{ApiSnapGenshin}/enka";
|
||||
private const string HomaSnapGenshin = "https://homa.snapgenshin.com";
|
||||
private const string EnkaHutao = "https://enka-api.hut.ao";
|
||||
}
|
||||
Reference in New Issue
Block a user