Compare commits

..

1 Commits

Author SHA1 Message Date
qhy040404
ceb26bb669 impl #1334 2024-02-01 22:19:35 +08:00
17 changed files with 164 additions and 800 deletions

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Model.Entity.Database;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
@@ -12,12 +11,13 @@ namespace Snap.Hutao.Core.Database;
internal sealed class ObservableReorderableDbCollection<T> : ObservableCollection<T>
where T : class, IReorderable
{
private readonly IServiceProvider serviceProvider;
private readonly DbContext dbContext;
private bool previousChangeIsRemoved;
public ObservableReorderableDbCollection(List<T> items, IServiceProvider serviceProvider)
public ObservableReorderableDbCollection(List<T> items, DbContext dbContext)
: base(AdjustIndex(items))
{
this.serviceProvider = serviceProvider;
this.dbContext = dbContext;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
@@ -27,8 +27,16 @@ internal sealed class ObservableReorderableDbCollection<T> : ObservableCollectio
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
previousChangeIsRemoved = true;
break;
case NotifyCollectionChangedAction.Add:
if (!previousChangeIsRemoved)
{
return;
}
OnReorder();
previousChangeIsRemoved = false;
break;
}
}
@@ -49,15 +57,10 @@ internal sealed class ObservableReorderableDbCollection<T> : ObservableCollectio
{
AdjustIndex((List<T>)Items);
using (IServiceScope scope = serviceProvider.CreateScope())
DbSet<T> dbSet = dbContext.Set<T>();
foreach (ref readonly T item in CollectionsMarshal.AsSpan((List<T>)Items))
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
DbSet<T> dbSet = appDbContext.Set<T>();
foreach (ref readonly T item in CollectionsMarshal.AsSpan((List<T>)Items))
{
dbSet.UpdateAndSave(item);
}
dbSet.UpdateAndSave(item);
}
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Text;
@@ -114,13 +113,6 @@ internal static partial class EnumerableExtension
return new ObservableCollection<T>(source);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ObservableReorderableDbCollection<T> ToObservableReorderableDbCollection<T>(this IEnumerable<T> source, IServiceProvider serviceProvider)
where T : class, IReorderable
{
return new ObservableReorderableDbCollection<T>([.. source], serviceProvider);
}
/// <summary>
/// Concatenates each element from the collection into single string.
/// </summary>

View File

@@ -1,624 +0,0 @@
// <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("20240202015857_ImplReorderableOnUserAndGameAccount")]
partial class ImplReorderableOnUserAndGameAccount
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
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>("SToken")
.HasColumnType("TEXT")
.HasColumnName("Stoken");
b.HasKey("InnerId");
b.ToTable("users");
});
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

@@ -1,60 +0,0 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Snap.Hutao.Migrations
{
/// <inheritdoc />
public partial class ImplReorderableOnUserAndGameAccount : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations");
migrationBuilder.AddColumn<int>(
name: "Index",
table: "users",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "Index",
table: "game_accounts",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations",
column: "EntryId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations");
migrationBuilder.DropColumn(
name: "Index",
table: "users");
migrationBuilder.DropColumn(
name: "Index",
table: "game_accounts");
migrationBuilder.CreateIndex(
name: "IX_cultivate_entry_level_informations_EntryId",
table: "cultivate_entry_level_informations",
column: "EntryId");
}
}
}

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.1");
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
@@ -42,7 +42,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("achievements");
b.ToTable("achievements", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
@@ -60,7 +60,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("achievement_archives");
b.ToTable("achievement_archives", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
@@ -88,7 +88,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("avatar_infos");
b.ToTable("avatar_infos", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
@@ -110,7 +110,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("cultivate_entries");
b.ToTable("cultivate_entries", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
@@ -154,10 +154,9 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.HasIndex("EntryId")
.IsUnique();
b.HasIndex("EntryId");
b.ToTable("cultivate_entry_level_informations");
b.ToTable("cultivate_entry_level_informations", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
@@ -182,7 +181,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("EntryId");
b.ToTable("cultivate_items");
b.ToTable("cultivate_items", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
@@ -203,7 +202,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("cultivate_projects");
b.ToTable("cultivate_projects", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
@@ -259,7 +258,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("UserId");
b.ToTable("daily_notes");
b.ToTable("daily_notes", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
@@ -277,7 +276,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("gacha_archives");
b.ToTable("gacha_archives", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
@@ -308,7 +307,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("gacha_items");
b.ToTable("gacha_items", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
@@ -320,9 +319,6 @@ namespace Snap.Hutao.Migrations
b.Property<string>("AttachUid")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<string>("MihoyoSDK")
.IsRequired()
.HasColumnType("TEXT");
@@ -336,7 +332,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("game_accounts");
b.ToTable("game_accounts", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
@@ -358,7 +354,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_items");
b.ToTable("inventory_items", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
@@ -387,7 +383,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_reliquaries");
b.ToTable("inventory_reliquaries", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
@@ -412,7 +408,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_weapons");
b.ToTable("inventory_weapons", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
@@ -428,7 +424,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("object_cache");
b.ToTable("object_cache", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
@@ -441,7 +437,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("settings");
b.ToTable("settings", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
@@ -463,7 +459,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("spiral_abysses");
b.ToTable("spiral_abysses", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
@@ -487,9 +483,6 @@ namespace Snap.Hutao.Migrations
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
.HasColumnType("TEXT");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<bool>("IsOversea")
.HasColumnType("INTEGER");
@@ -509,7 +502,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("users");
b.ToTable("users", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
@@ -537,8 +530,8 @@ namespace Snap.Hutao.Migrations
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")
.WithMany()
.HasForeignKey("EntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -610,11 +603,6 @@ namespace Snap.Hutao.Migrations
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.Navigation("LevelInformation");
});
#pragma warning restore 612, 618
}
}

View File

@@ -3,7 +3,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Primitive;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -15,7 +14,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("game_accounts")]
internal sealed class GameAccount : ObservableObject, IReorderable, IMappingFrom<GameAccount, string, string, SchemeType>
internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount, string, string, SchemeType>, ICloneable<GameAccount>
{
/// <summary>
/// 内部Id
@@ -45,8 +44,6 @@ internal sealed class GameAccount : ObservableObject, IReorderable, IMappingFrom
/// </summary>
public string MihoyoSDK { get; set; } = default!;
public int Index { get; set; }
public static GameAccount From(string name, string sdk, SchemeType type)
{
return new()
@@ -57,6 +54,17 @@ internal sealed class GameAccount : ObservableObject, IReorderable, IMappingFrom
};
}
public GameAccount Clone()
{
return new()
{
AttachUid = AttachUid,
Name = Name,
MihoyoSDK = MihoyoSDK,
Type = Type,
};
}
/// <summary>
/// 更新绑定的Uid
/// </summary>

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("users")]
internal sealed class User : ISelectable, IReorderable, IMappingFrom<User, Cookie, bool>
internal sealed class User : ISelectable, IMappingFrom<User, Cookie, bool>
{
/// <summary>
/// 内部Id
@@ -69,8 +69,6 @@ internal sealed class User : ISelectable, IReorderable, IMappingFrom<User, Cooki
public DateTimeOffset CookieTokenLastUpdateTime { get; set; }
public int Index { get; set; }
/// <summary>
/// 创建一个新的用户
/// </summary>

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Model.Entity;
@@ -19,9 +18,9 @@ internal sealed partial class GameAccountService : IGameAccountService
private readonly IGameDbService gameDbService;
private readonly ITaskContext taskContext;
private ObservableReorderableDbCollection<GameAccount>? gameAccounts;
private ObservableCollection<GameAccount>? gameAccounts;
public ObservableReorderableDbCollection<GameAccount> GameAccountCollection
public ObservableCollection<GameAccount> GameAccountCollection
{
get => gameAccounts ??= gameDbService.GetGameAccountCollection();
}
@@ -121,6 +120,11 @@ internal sealed partial class GameAccountService : IGameAccountService
await gameDbService.RemoveGameAccountByIdAsync(gameAccount.InnerId).ConfigureAwait(false);
}
public void ReorderGameAccounts(IEnumerable<GameAccount> reorderedGameAccounts)
{
gameDbService.ReorderGameAccounts(reorderedGameAccounts);
}
private static GameAccount? SingleGameAccountOrDefault(ObservableCollection<GameAccount> gameAccounts, string registrySdk)
{
try

View File

@@ -1,15 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Primitive;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Game.Account;
internal interface IGameAccountService
{
ObservableReorderableDbCollection<GameAccount> GameAccountCollection { get; }
ObservableCollection<GameAccount> GameAccountCollection { get; }
void AttachGameAccountToUid(GameAccount gameAccount, string uid);
@@ -22,4 +22,6 @@ internal interface IGameAccountService
ValueTask RemoveGameAccountAsync(GameAccount gameAccount);
bool SetGameAccount(GameAccount account);
void ReorderGameAccounts(IEnumerable<GameAccount> reorderedGameAccounts);
}

View File

@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Game;
@@ -14,12 +15,12 @@ internal sealed partial class GameDbService : IGameDbService
{
private readonly IServiceProvider serviceProvider;
public ObservableReorderableDbCollection<GameAccount> GetGameAccountCollection()
public ObservableCollection<GameAccount> GetGameAccountCollection()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.GameAccounts.AsNoTracking().OrderBy(account => account.Index).ToObservableReorderableDbCollection(serviceProvider);
return appDbContext.GameAccounts.AsNoTracking().ToObservableCollection();
}
}
@@ -58,4 +59,19 @@ internal sealed partial class GameDbService : IGameDbService
await appDbContext.GameAccounts.ExecuteDeleteWhereAsync(a => a.InnerId == id).ConfigureAwait(false);
}
}
public void ReorderGameAccounts(IEnumerable<GameAccount> reorderedGameAccounts)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
appDbContext.GameAccounts.ExecuteDelete();
// Use foreach to avoid ORDER BY InnerId
foreach (GameAccount gameAccount in reorderedGameAccounts)
{
appDbContext.GameAccounts.AddAndSave(gameAccount);
}
}
}
}

View File

@@ -1,13 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Service.Game.Account;
using Snap.Hutao.Service.Game.Configuration;
using Snap.Hutao.Service.Game.Launching.Handler;
using Snap.Hutao.Service.Game.PathAbstraction;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Game;
@@ -24,7 +24,7 @@ internal sealed partial class GameServiceFacade : IGameServiceFacade
private readonly IGamePathService gamePathService;
/// <inheritdoc/>
public ObservableReorderableDbCollection<GameAccount> GameAccountCollection
public ObservableCollection<GameAccount> GameAccountCollection
{
get => gameAccountService.GameAccountCollection;
}
@@ -76,4 +76,9 @@ internal sealed partial class GameServiceFacade : IGameServiceFacade
{
return LaunchExecutionEnsureGameNotRunningHandler.IsGameRunning(out _);
}
public void ReorderGameAccounts(IEnumerable<GameAccount> reorderedGameAccounts)
{
gameAccountService.ReorderGameAccounts(reorderedGameAccounts);
}
}

View File

@@ -1,8 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Game;
@@ -12,9 +12,11 @@ internal interface IGameDbService
ValueTask RemoveGameAccountByIdAsync(Guid id);
ObservableReorderableDbCollection<GameAccount> GetGameAccountCollection();
ObservableCollection<GameAccount> GetGameAccountCollection();
void UpdateGameAccount(GameAccount gameAccount);
ValueTask UpdateGameAccountAsync(GameAccount gameAccount);
void ReorderGameAccounts(IEnumerable<GameAccount> reorderedGameAccounts);
}

View File

@@ -1,10 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Service.Game.Configuration;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Game;
@@ -17,7 +17,7 @@ internal interface IGameServiceFacade
/// <summary>
/// 游戏内账号集合
/// </summary>
ObservableReorderableDbCollection<GameAccount> GameAccountCollection { get; }
ObservableCollection<GameAccount> GameAccountCollection { get; }
/// <summary>
/// 将账号绑定到对应的Uid
@@ -62,4 +62,6 @@ internal interface IGameServiceFacade
ValueTask RemoveGameAccountAsync(GameAccount gameAccount);
GameAccount? DetectCurrentGameAccount(SchemeType scheme);
void ReorderGameAccounts(IEnumerable<GameAccount> reorderedGameAccounts);
}

View File

@@ -149,22 +149,20 @@
</DataTemplate>
<DataTemplate x:Key="AnnouncementPivotItemContentTemplate">
<ScrollViewer>
<ItemsRepeater
Margin="16,16,16,16"
HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource AnnouncementTemplate}"
ItemsSource="{Binding List}">
<ItemsRepeater.Layout>
<UniformGridLayout
ItemsJustification="Start"
ItemsStretch="Fill"
MinColumnSpacing="12"
MinItemWidth="300"
MinRowSpacing="12"/>
</ItemsRepeater.Layout>
</ItemsRepeater>
</ScrollViewer>
<ItemsRepeater
Margin="16,16,16,16"
HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource AnnouncementTemplate}"
ItemsSource="{Binding List}">
<ItemsRepeater.Layout>
<UniformGridLayout
ItemsJustification="Start"
ItemsStretch="Fill"
MinColumnSpacing="12"
MinItemWidth="300"
MinRowSpacing="12"/>
</ItemsRepeater.Layout>
</ItemsRepeater>
</DataTemplate>
<DataTemplate x:Key="HutaoAnnouncementTemplate">
@@ -194,7 +192,7 @@
<SolidColorBrush x:Key="ItemContainerPointerOverBackground" Color="Transparent"/>
</ItemContainer.Resources>
<Border Style="{ThemeResource AcrylicBorderCardStyle}">
<Border Background="{ThemeResource SystemControlAcrylicElementMediumHighBrush}" CornerRadius="{ThemeResource ControlCornerRadius}">
<ContentControl HorizontalContentAlignment="Stretch" Content="{Binding Card}"/>
</Border>
</ItemContainer>
@@ -233,16 +231,21 @@
</ItemsRepeater>
</StackPanel>
<Pivot Style="{StaticResource DefaultPivotStyle}">
<PivotItem
Content="{Binding Announcement.List[0]}"
ContentTemplate="{StaticResource AnnouncementPivotItemContentTemplate}"
Header="{shcm:ResourceString Name=ViewPageAnnouncementActivity}"/>
<PivotItem
Content="{Binding Announcement.List[1]}"
ContentTemplate="{StaticResource AnnouncementPivotItemContentTemplate}"
Header="{shcm:ResourceString Name=ViewPageAnnouncementGame}"/>
</Pivot>
<ScrollViewer
HorizontalScrollBarVisibility="Disabled"
HorizontalScrollMode="Disabled"
VerticalScrollBarVisibility="Hidden">
<Pivot Style="{StaticResource DefaultPivotStyle}">
<PivotItem
Content="{Binding Announcement.List[0]}"
ContentTemplate="{StaticResource AnnouncementPivotItemContentTemplate}"
Header="{shcm:ResourceString Name=ViewPageAnnouncementActivity}"/>
<PivotItem
Content="{Binding Announcement.List[1]}"
ContentTemplate="{StaticResource AnnouncementPivotItemContentTemplate}"
Header="{shcm:ResourceString Name=ViewPageAnnouncementGame}"/>
</Pivot>
</ScrollViewer>
</StackPanel>
</ScrollViewer>

View File

@@ -203,7 +203,9 @@
<Border Padding="0,1" Style="{StaticResource BorderCardStyle}">
<ListView
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="OnAccountListViewReordered"
ItemTemplate="{StaticResource GameAccountListTemplate}"
ItemsSource="{Binding GameAccountsView}"
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>

View File

@@ -1,8 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.ViewModel.Game;
using System.Collections.ObjectModel;
namespace Snap.Hutao.View.Page;
@@ -20,4 +24,20 @@ internal sealed partial class LaunchGamePage : ScopedPage
InitializeWith<LaunchGameViewModel>();
InitializeComponent();
}
public void OnAccountListViewReordered(object sender, DragItemsCompletedEventArgs e)
{
ListView listView = (ListView)sender;
AdvancedCollectionView advancedCollectionView = (AdvancedCollectionView)listView.ItemsSource;
ObservableCollection<GameAccount> gameAccounts = (ObservableCollection<GameAccount>)advancedCollectionView.Source;
List<GameAccount> newGameAccounts = [];
foreach (GameAccount gameAccount in gameAccounts)
{
newGameAccounts.Add(gameAccount.Clone());
}
((LaunchGameViewModel)DataContext).ReorderGameAccounts(newGameAccounts);
}
}

View File

@@ -5,7 +5,6 @@ using CommunityToolkit.WinUI.Collections;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.UI.Windowing;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics.CodeAnalysis;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Entity;
@@ -19,6 +18,7 @@ using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.IO;
namespace Snap.Hutao.ViewModel.Game;
@@ -172,6 +172,11 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
SelectedGamePathEntry = selectedEntry;
}
public void ReorderGameAccounts(IEnumerable<GameAccount> reorderedGameAccounts)
{
gameService.ReorderGameAccounts(reorderedGameAccounts);
}
protected override ValueTask<bool> InitializeUIAsync()
{
ImmutableList<GamePathEntry> gamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
@@ -241,8 +246,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
{
await taskContext.SwitchToMainThreadAsync();
SelectedGameAccount = account;
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
}
}
catch (UserdataCorruptedException ex)
@@ -328,6 +331,18 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
GameResource = response.Data;
}
}
async ValueTask UpdateGameAccountsViewAsync()
{
gameAccountFilter = new(SelectedScheme?.GetSchemeType());
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
await taskContext.SwitchToMainThreadAsync();
GameAccountsView = new(accounts, true)
{
Filter = gameAccountFilter.Filter,
};
}
}
[Command("IdentifyMonitorsCommand")]
@@ -353,16 +368,4 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
window.Close();
}
}
private async ValueTask UpdateGameAccountsViewAsync()
{
gameAccountFilter = new(SelectedScheme?.GetSchemeType());
ObservableReorderableDbCollection<GameAccount> accounts = gameService.GameAccountCollection;
await taskContext.SwitchToMainThreadAsync();
GameAccountsView = new(accounts, true)
{
Filter = gameAccountFilter.Filter,
};
}
}