Compare commits

...

5 Commits

Author SHA1 Message Date
qhy040404
8fbd648b1b add profile picture for each game role 2024-06-14 01:09:32 +08:00
qhy040404
107963b7ac Update issue template 2024-06-13 18:39:03 +08:00
DismissedLight
4e89406f2f Merge pull request #1721 from DGP-Studio/feat/1715 2024-06-13 16:15:21 +08:00
Lightczx
8119de3fa9 code style 2024-06-13 16:15:08 +08:00
qhy040404
7a8c233b10 review requests 2024-06-13 15:36:50 +08:00
34 changed files with 1244 additions and 35 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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"/>

View 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
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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")

View File

@@ -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>()

View File

@@ -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,
};
}
}

View File

@@ -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; }
}

View File

@@ -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);
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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);

View File

@@ -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";

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -53,4 +53,6 @@ internal interface IUserService
/// <param name="user">待移除的用户</param>
/// <returns>任务</returns>
ValueTask RemoveUserAsync(BindingUser user);
ValueTask RefreshUserGameRoleProfilePictureAsync(UserGameRole userGameRole);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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!;
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -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="&#xE72C;"
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">

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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; }
}

View File

@@ -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>

View File

@@ -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";
}