remove visual transition gap in gacha log initialization

This commit is contained in:
DismissedLight
2023-01-23 12:58:00 +08:00
parent 0d34c81bcf
commit 623893e00e
16 changed files with 426 additions and 370 deletions

View File

@@ -119,8 +119,8 @@ internal static class CoreEnvironment
{
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
#if RELEASE
// 将测试版与正式版的文件目录分离
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
// 将测试版与正式版的文件目录分离
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
#else
// 使得迁移能正常生成
string folderName = "Hutao";

View File

@@ -149,7 +149,7 @@ internal static class Activation
Ioc.Default
.GetRequiredService<IMetadataService>()
.ImplictAs<IMetadataInitializer>()?
.ImplictAs<IMetadataServiceInitialization>()?
.InitializeInternalAsync()
.SafeForget();
}

View File

@@ -6,9 +6,30 @@ using System.Collections.Concurrent;
namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 并发<see cref="CancellationTokenSource"/>
/// 无区分项的并发<see cref="CancellationTokenSource"/>
/// </summary>
[SuppressMessage("", "CA1001")]
internal class ConcurrentCancellationTokenSource
{
private CancellationTokenSource source = new();
/// <summary>
/// 注册取消令牌
/// </summary>
/// <returns>取消令牌</returns>
public CancellationToken Register()
{
source.Cancel();
source = new();
return source.Token;
}
}
/// <summary>
/// 有区分项的并发<see cref="CancellationTokenSource"/>
/// </summary>
/// <typeparam name="TItem">项类型</typeparam>
[SuppressMessage("", "SA1402")]
internal class ConcurrentCancellationTokenSource<TItem>
where TItem : notnull
{
@@ -17,7 +38,7 @@ internal class ConcurrentCancellationTokenSource<TItem>
/// <summary>
/// 为某个项注册取消令牌
/// </summary>
/// <param name="item">项</param>
/// <param name="item">区分项</param>
/// <returns>取消令牌</returns>
public CancellationToken Register(TItem item)
{
@@ -28,4 +49,4 @@ internal class ConcurrentCancellationTokenSource<TItem>
return waitingItems.GetOrAdd(item, new CancellationTokenSource()).Token;
}
}
}

View File

@@ -4,7 +4,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Service.Cultivation;
namespace Snap.Hutao.Model.Binding.Cultivation;
@@ -25,7 +24,7 @@ public class CultivateItem : ObservableObject
Inner = inner;
Entity = entity;
isFinished = Entity.IsFinished;
IsToday = CultivateItemHelper.IsTodaysMaterial(inner.Id, DateTimeOffset.Now);
IsToday = inner.IsTodaysItem();
FinishStateCommand = new RelayCommand(FlipIsFinished);
}
@@ -55,7 +54,6 @@ public class CultivateItem : ObservableObject
if (SetProperty(ref isFinished, value))
{
Entity.IsFinished = value;
Ioc.Default.GetRequiredService<ICultivationService>().SaveCultivateItem(Entity);
}
}
}

View File

@@ -1,64 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Binding.Cultivation;
/// <summary>
/// 养成物品帮助类
/// </summary>
public static class CultivateItemHelper
{
/// <summary>
/// 判断是否为当日物品
/// </summary>
/// <param name="itemId">材料Id</param>
/// <param name="now">时间</param>
/// <returns>是否为当日物品</returns>
public static bool IsTodaysMaterial(int itemId, DateTimeOffset now)
{
DateTimeOffset utcNow = now.ToUniversalTime();
utcNow = utcNow.AddHours(4);
DayOfWeek dayOfWeek = utcNow.DayOfWeek;
return dayOfWeek switch
{
DayOfWeek.Monday or DayOfWeek.Thursday => itemId switch
{
104301 or 104302 or 104303 => true, // 「自由」
104310 or 104311 or 104312 => true, // 「繁荣」
104320 or 104321 or 104322 => true, // 「浮世」
104329 or 104330 or 104331 => true, // 「诤言」
114001 or 114002 or 114003 or 114004 => true, // 高塔孤王
114013 or 114014 or 114015 or 114016 => true, // 孤云寒林
114025 or 114026 or 114027 or 114028 => true, // 远海夷地
114037 or 114038 or 114039 or 114040 => true, // 谧林涓露
_ => false,
},
DayOfWeek.Tuesday or DayOfWeek.Friday => itemId switch
{
104304 or 104305 or 104306 => true, // 「抗争」
104313 or 104314 or 104315 => true, // 「勤劳」
104323 or 104324 or 104325 => true, // 「风雅」
104332 or 104333 or 104334 => true, // 「巧思」
114005 or 114006 or 114007 or 114008 => true, // 凛风奔狼
114017 or 114018 or 114019 or 114020 => true, // 雾海云间
114029 or 114030 or 114031 or 114032 => true, // 鸣神御灵
114041 or 114042 or 114043 or 114044 => true, // 绿洲花园
_ => false,
},
DayOfWeek.Wednesday or DayOfWeek.Saturday => itemId switch
{
104307 or 104308 or 104309 => true, // 「诗文」
104316 or 104317 or 104318 => true, // 「黄金」
104326 or 104327 or 104328 => true, // 「天光」
104335 or 104336 or 104337 => true, // 「笃行」
114009 or 114010 or 114011 or 114012 => true, // 狮牙斗士
114021 or 114022 or 114023 or 114024 => true, // 漆黑陨铁
114033 or 114034 or 114035 or 114036 => true, // 今昔剧画
114045 or 114046 or 114047 or 114048 => true, // 谧林涓露
_ => false,
},
_ => false,
};
}
}

View File

@@ -13,47 +13,47 @@ public enum MaterialType
MATERIAL_FOOD = 1,
MATERIAL_QUEST = 2,
MATERIAL_EXCHANGE = 4,
MATERIAL_CONSUME,
MATERIAL_EXP_FRUIT,
MATERIAL_AVATAR,
MATERIAL_ADSORBATE,
MATERIAL_CRICKET,
MATERIAL_ELEM_CRYSTAL,
MATERIAL_WEAPON_EXP_STONE,
MATERIAL_CHEST,
MATERIAL_RELIQUARY_MATERIAL,
MATERIAL_AVATAR_MATERIAL,
MATERIAL_NOTICE_ADD_HP,
MATERIAL_SEA_LAMP,
MATERIAL_SELECTABLE_CHEST,
MATERIAL_FLYCLOAK,
MATERIAL_NAMECARD,
MATERIAL_TALENT,
MATERIAL_WIDGET,
MATERIAL_CHEST_BATCH_USE,
MATERIAL_FAKE_ABSORBATE,
MATERIAL_CONSUME_BATCH_USE,
MATERIAL_WOOD,
MATERIAL_CONSUME = 5,
MATERIAL_EXP_FRUIT = 6,
MATERIAL_AVATAR = 7,
MATERIAL_ADSORBATE = 8,
MATERIAL_CRICKET = 9,
MATERIAL_ELEM_CRYSTAL = 10,
MATERIAL_WEAPON_EXP_STONE = 11,
MATERIAL_CHEST = 12,
MATERIAL_RELIQUARY_MATERIAL = 13,
MATERIAL_AVATAR_MATERIAL = 14,
MATERIAL_NOTICE_ADD_HP = 15,
MATERIAL_SEA_LAMP = 16,
MATERIAL_SELECTABLE_CHEST = 17,
MATERIAL_FLYCLOAK = 18,
MATERIAL_NAMECARD = 19,
MATERIAL_TALENT = 20,
MATERIAL_WIDGET = 21,
MATERIAL_CHEST_BATCH_USE = 22,
MATERIAL_FAKE_ABSORBATE = 23,
MATERIAL_CONSUME_BATCH_USE = 24,
MATERIAL_WOOD = 25,
MATERIAL_FURNITURE_FORMULA = 27,
MATERIAL_CHANNELLER_SLAB_BUFF,
MATERIAL_FURNITURE_SUITE_FORMULA,
MATERIAL_COSTUME,
MATERIAL_HOME_SEED,
MATERIAL_FISH_BAIT,
MATERIAL_FISH_ROD,
MATERIAL_SUMO_BUFF, // never appear
MATERIAL_FIREWORKS,
MATERIAL_BGM,
MATERIAL_SPICE_FOOD,
MATERIAL_ACTIVITY_ROBOT,
MATERIAL_ACTIVITY_GEAR,
MATERIAL_ACTIVITY_JIGSAW,
MATERIAL_ARANARA,
MATERIAL_GCG_CARD,
MATERIAL_GCG_CARD_FACE, // 影幻卡面
MATERIAL_GCG_CARD_BACK,
MATERIAL_GCG_FIELD,
MATERIAL_DESHRET_MANUAL,
MATERIAL_RENAME_ITEM,
MATERIAL_GCG_EXCHANGE_ITEM,
MATERIAL_CHANNELLER_SLAB_BUFF = 28,
MATERIAL_FURNITURE_SUITE_FORMULA = 29,
MATERIAL_COSTUME = 30,
MATERIAL_HOME_SEED = 31,
MATERIAL_FISH_BAIT = 32,
MATERIAL_FISH_ROD = 33,
MATERIAL_SUMO_BUFF = 34, // never appear
MATERIAL_FIREWORKS = 35,
MATERIAL_BGM = 36,
MATERIAL_SPICE_FOOD = 37,
MATERIAL_ACTIVITY_ROBOT = 38,
MATERIAL_ACTIVITY_GEAR = 39,
MATERIAL_ACTIVITY_JIGSAW = 40,
MATERIAL_ARANARA = 41,
MATERIAL_GCG_CARD = 42,
MATERIAL_GCG_CARD_FACE = 43, // 影幻卡面
MATERIAL_GCG_CARD_BACK = 44,
MATERIAL_GCG_FIELD = 45,
MATERIAL_DESHRET_MANUAL = 46,
MATERIAL_RENAME_ITEM = 47,
MATERIAL_GCG_EXCHANGE_ITEM = 48,
}

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
using System.Collections.Immutable;
namespace Snap.Hutao.Model.Metadata;
@@ -11,6 +12,42 @@ namespace Snap.Hutao.Model.Metadata;
/// </summary>
public class Material
{
private static readonly ImmutableHashSet<MaterialId> MondayThursdayItems = new HashSet<MaterialId>
{
104301, 104302, 104303, // 「自由」
104310, 104311, 104312, // 「繁荣」
104320, 104321, 104322, // 「浮世」
104329, 104330, 104331, // 「诤言」
114001, 114002, 114003, 114004, // 高塔孤王
114013, 114014, 114015, 114016, // 孤云寒林
114025, 114026, 114027, 114028, // 远海夷地
114037, 114038, 114039, 114040, // 谧林涓露
}.ToImmutableHashSet();
private static readonly ImmutableHashSet<MaterialId> TuesdayFridayItems = new HashSet<MaterialId>
{
104304, 104305, 104306, // 「抗争」
104313, 104314, 104315, // 「勤劳」
104323, 104324, 104325, // 「风雅」
104332, 104333, 104334, // 「巧思」
114005, 114006, 114007, 114008, // 凛风奔狼
114017, 114018, 114019, 114020, // 雾海云间
114029, 114030, 114031, 114032, // 鸣神御灵
114041, 114042, 114043, 114044, // 绿洲花园
}.ToImmutableHashSet();
private static readonly ImmutableHashSet<MaterialId> WednesdaySaturdayItems = new HashSet<MaterialId>
{
104307, 104308, 104309, // 「诗文」
104316, 104317, 104318, // 「黄金」
104326, 104327, 104328, // 「天光」
104335, 104336, 104337, // 「笃行」
114009, 114010, 114011, 114012, // 狮牙斗士
114021, 114022, 114023, 114024, // 漆黑陨铁
114033, 114034, 114035, 114036, // 今昔剧画
114045, 114046, 114047, 114048, // 谧林涓露
}.ToImmutableHashSet();
/// <summary>
/// 物品Id
/// </summary>
@@ -89,4 +126,21 @@ public class Material
_ => false,
};
}
/// <summary>
/// 判断是否为当日物品
/// O(1) 操作
/// </summary>
/// <param name="treatSundayAsTrue">星期日视为当日材料</param>
/// <returns>是否为当日物品</returns>
public bool IsTodaysItem(bool treatSundayAsTrue = false)
{
return DateTimeOffset.UtcNow.AddHours(4).DayOfWeek switch
{
DayOfWeek.Monday or DayOfWeek.Thursday => MondayThursdayItems.Contains(Id),
DayOfWeek.Tuesday or DayOfWeek.Friday => TuesdayFridayItems.Contains(Id),
DayOfWeek.Wednesday or DayOfWeek.Saturday => WednesdaySaturdayItems.Contains(Id),
_ => false,
};
}
}

View File

@@ -12,7 +12,7 @@
<Identity
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
Publisher="CN=DGP Studio"
Version="1.3.12.0" />
Version="1.3.13.0" />
<Properties>
<DisplayName>胡桃</DisplayName>

View File

@@ -5,13 +5,16 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata;
using System.Collections.ObjectModel;
using BindingCultivateEntry = Snap.Hutao.Model.Binding.Cultivation.CultivateEntry;
using BindingCultivateItem = Snap.Hutao.Model.Binding.Cultivation.CultivateItem;
using BindingInventoryItem = Snap.Hutao.Model.Binding.Inventory.InventoryItem;
using BindingStatisticsItem = Snap.Hutao.Model.Binding.Cultivation.StatisticsCultivateItem;
namespace Snap.Hutao.Service.Cultivation;
@@ -129,22 +132,21 @@ internal class CultivationService : ICultivationService
}
/// <inheritdoc/>
public async Task<ObservableCollection<BindingCultivateEntry>> GetCultivateEntriesAsync(
CultivateProject cultivateProject,
List<Model.Metadata.Material> materials,
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap,
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap)
public async Task<ObservableCollection<BindingCultivateEntry>> GetCultivateEntriesAsync(CultivateProject cultivateProject)
{
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
IMetadataService metadataService = scope.ServiceProvider.GetRequiredService<IMetadataService>();
Guid projectId = cultivateProject.InnerId;
List<Model.Metadata.Material> materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false);
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
List<BindingCultivateEntry> results = new();
List<CultivateEntry> entries = await appDbContext.CultivateEntries
.Where(e => e.ProjectId == projectId)
.Where(e => e.ProjectId == cultivateProject.InnerId)
.ToListAsync()
.ConfigureAwait(false);
@@ -153,13 +155,8 @@ internal class CultivationService : ICultivationService
Guid entryId = entry.InnerId;
List<BindingCultivateItem> resultItems = new();
List<CultivateItem> items = await appDbContext.CultivateItems
.Where(i => i.EntryId == entryId)
.OrderBy(i => i.ItemId)
.ToListAsync()
.ConfigureAwait(false);
foreach (CultivateItem item in items)
foreach (CultivateItem item in await GetEntryItemsAsync(appDbContext, entryId).ConfigureAwait(false))
{
resultItems.Add(new(materials.Single(m => m.Id == item.ItemId), item));
}
@@ -174,71 +171,64 @@ internal class CultivationService : ICultivationService
results.Add(new(entry, itemBase, resultItems));
}
return new(results.OrderByDescending(e => e.Items.Any(i => i.IsToday)));
return results
.OrderByDescending(e => e.Items.Any(i => i.IsToday))
.ToObservableCollection();
}
}
/// <inheritdoc/>
public async Task<List<Model.Binding.Cultivation.StatisticsCultivateItem>> GetStatisticsCultivateItemsAsync(CultivateProject cultivateProject, List<Model.Metadata.Material> materials)
public async Task<ObservableCollection<BindingStatisticsItem>> GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, CancellationToken token)
{
using (IServiceScope scope = scopeFactory.CreateScope())
{
List<BindingStatisticsItem> resultItems = new();
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
List<Model.Metadata.Material> materials = await scope.ServiceProvider
.GetRequiredService<IMetadataService>()
.GetMaterialsAsync(default)
.ConfigureAwait(false);
Guid projectId = cultivateProject.InnerId;
List<Model.Binding.Cultivation.StatisticsCultivateItem> resultItems = new();
token.ThrowIfCancellationRequested();
List<CultivateEntry> entries = await appDbContext.CultivateEntries
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
foreach (CultivateEntry entry in entries)
foreach (CultivateEntry entry in await GetProjectEntriesAsync(appDbContext, projectId).ConfigureAwait(false))
{
Guid entryId = entry.InnerId;
List<CultivateItem> items = await appDbContext.CultivateItems
.AsNoTracking()
.Where(i => i.EntryId == entryId)
.OrderBy(i => i.ItemId)
.ToListAsync()
.ConfigureAwait(false);
foreach (CultivateItem item in items)
foreach (CultivateItem item in await GetEntryItemsAsync(appDbContext, entry.InnerId).ConfigureAwait(false))
{
if (item.IsFinished)
{
continue;
}
if (resultItems.SingleOrDefault(i => i.Inner.Id == item.ItemId) is Model.Binding.Cultivation.StatisticsCultivateItem inPlaceItem)
if (resultItems.SingleOrDefault(i => i.Inner.Id == item.ItemId) is BindingStatisticsItem existedItem)
{
inPlaceItem.Count += item.Count;
existedItem.Count += item.Count;
}
else
{
resultItems.Add(new(materials.Single(m => m.Id == item.ItemId), item));
resultItems.Add(new(materials!.Single(m => m.Id == item.ItemId), item));
}
}
}
List<InventoryItem> inventoryItems = await appDbContext.InventoryItems
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
token.ThrowIfCancellationRequested();
foreach (InventoryItem inventoryItem in inventoryItems)
foreach (InventoryItem inventoryItem in await GetProjectInventoryAsync(appDbContext, projectId).ConfigureAwait(false))
{
if (resultItems.SingleOrDefault(i => i.Inner.Id == inventoryItem.ItemId) is Model.Binding.Cultivation.StatisticsCultivateItem inPlaceItem)
if (resultItems.SingleOrDefault(i => i.Inner.Id == inventoryItem.ItemId) is BindingStatisticsItem existedItem)
{
inPlaceItem.TotalCount += inventoryItem.Count;
existedItem.TotalCount += inventoryItem.Count;
}
}
return resultItems.OrderByDescending(i => i.Count).ToList();
token.ThrowIfCancellationRequested();
await ThreadHelper.SwitchToMainThreadAsync();
return resultItems.OrderByDescending(i => i.Count).ToObservableCollection();
}
}
@@ -246,9 +236,11 @@ internal class CultivationService : ICultivationService
public async Task RemoveCultivateEntryAsync(Guid entryId)
{
await ThreadHelper.SwitchToBackgroundAsync();
IEnumerable<CultivateItem> removed;
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
removed = await GetEntryItemsAsync(appDbContext, entryId).ConfigureAwait(false);
await appDbContext.CultivateEntries.Where(i => i.InnerId == entryId).ExecuteDeleteAsync().ConfigureAwait(false);
}
}
@@ -309,4 +301,29 @@ internal class CultivationService : ICultivationService
return true;
}
private static Task<List<InventoryItem>> GetProjectInventoryAsync(AppDbContext appDbContext, Guid projectId)
{
return appDbContext.InventoryItems
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync();
}
private static Task<List<CultivateEntry>> GetProjectEntriesAsync(AppDbContext appDbContext, Guid projectId)
{
return appDbContext.CultivateEntries
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync();
}
private static Task<List<CultivateItem>> GetEntryItemsAsync(AppDbContext appDbContext, Guid entryId)
{
return appDbContext.CultivateItems
.AsNoTracking()
.Where(i => i.EntryId == entryId)
.OrderBy(i => i.ItemId)
.ToListAsync();
}
}

View File

@@ -24,11 +24,8 @@ internal interface ICultivationService
/// 获取绑定用的养成列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="materials">材料</param>
/// <param name="idAvatarMap">Id角色映射</param>
/// <param name="idWeaponMap">Id武器映射</param>
/// <returns>绑定用的养成列表</returns>
Task<ObservableCollection<Model.Binding.Cultivation.CultivateEntry>> GetCultivateEntriesAsync(CultivateProject cultivateProject, List<Material> materials, Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap, Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap);
Task<ObservableCollection<Model.Binding.Cultivation.CultivateEntry>> GetCultivateEntriesAsync(CultivateProject cultivateProject);
/// <summary>
/// 获取物品列表
@@ -48,9 +45,9 @@ internal interface ICultivationService
/// 异步获取统计物品列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="materials">元数据</param>
/// <param name="token">取消令牌</param>
/// <returns>统计物品列表</returns>
Task<List<StatisticsCultivateItem>> GetStatisticsCultivateItemsAsync(CultivateProject cultivateProject, List<Material> materials);
Task<ObservableCollection<StatisticsCultivateItem>> GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, CancellationToken token);
/// <summary>
/// 删除养成清单

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Service.Metadata;
/// <summary>
/// 指示该类为元数据初始化器
/// </summary>
public interface IMetadataInitializer
public interface IMetadataServiceInitialization
{
/// <summary>
/// 异步初始化元数据

View File

@@ -19,7 +19,7 @@ namespace Snap.Hutao.Service.Metadata;
/// </summary>
[Injection(InjectAs.Singleton, typeof(IMetadataService))]
[HttpClient(HttpClientConfigration.Default)]
internal partial class MetadataService : IMetadataService, IMetadataInitializer
internal partial class MetadataService : IMetadataService, IMetadataServiceInitialization
{
private const string MetaFileName = "Meta.json";

View File

@@ -42,6 +42,7 @@
<mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
</mxi:Interaction.Behaviors>
<Grid Visibility="{Binding IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid Visibility="{Binding Projects.Count, Converter={StaticResource Int32ToVisibilityConverter}}">
<Rectangle
@@ -52,54 +53,6 @@
<Pivot>
<Pivot.RightHeader>
<CommandBar DefaultLabelPosition="Right">
<AppBarButton
Command="{Binding UpdateStatisticsItemsCommand}"
Icon="{shcm:FontIcon Glyph=&#xEB05;}"
Label="材料统计">
<AppBarButton.Flyout>
<Flyout Placement="Bottom">
<Flyout.FlyoutPresenterStyle>
<Style BasedOn="{StaticResource DefaultFlyoutPresenterStyle}" TargetType="FlyoutPresenter">
<Setter Property="MaxHeight" Value="480"/>
<Setter Property="Padding" Value="0,2,0,2"/>
<Setter Property="Background" Value="{ThemeResource FlyoutPresenterBackground}"/>
</Style>
</Flyout.FlyoutPresenterStyle>
<ItemsControl Margin="16,0,16,16" ItemsSource="{Binding StatisticsItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,16,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="160"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:ItemIcon
Grid.Column="0"
Width="32"
Height="32"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<TextBlock
Grid.Column="1"
Margin="16,0,0,0"
VerticalAlignment="Center"
Text="{Binding Inner.Name}"
TextTrimming="CharacterEllipsis"/>
<TextBlock
Grid.Column="2"
Margin="16,0,4,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{Binding CountFormatted}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarSeparator/>
<AppBarElementContainer>
<ComboBox
Height="36"
@@ -127,149 +80,195 @@
<PivotItem Header="材料清单">
<Grid>
<cwucont:AdaptiveGridView
Padding="16,16,4,4"
cwua:ItemsReorderAnimation.Duration="0:0:0.1"
DesiredWidth="320"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemsSource="{Binding CultivateEntries}"
SelectionMode="None"
Visibility="{Binding CultivateEntries.Count, Converter={StaticResource Int32ToVisibilityConverter}}">
<cwucont:AdaptiveGridView.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource BorderCardStyle}">
<Grid Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid Margin="8">
<Pivot Visibility="{Binding CultivateEntries.Count, Converter={StaticResource Int32ToVisibilityConverter}}">
<PivotItem Header="材料统计">
<cwucont:AdaptiveGridView
Padding="16,16,4,4"
cwua:ItemsReorderAnimation.Duration="0:0:0.1"
DesiredWidth="320"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemsSource="{Binding StatisticsItems}"
SelectionMode="None">
<cwucont:AdaptiveGridView.Resources>
<x:Double x:Key="GridViewItemMinHeight">0</x:Double>
</cwucont:AdaptiveGridView.Resources>
<cwucont:AdaptiveGridView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<shvc:ItemIcon
Width="48"
Height="48"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
Grid.Column="0"
Width="32"
Height="32"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<TextBlock
Grid.Column="1"
Margin="8,0,0,0"
Margin="16,0,0,0"
VerticalAlignment="Center"
Text="{Binding Name}"/>
<StackPanel
x:Name="ButtonPanel"
Text="{Binding Inner.Name}"
TextTrimming="CharacterEllipsis"/>
<TextBlock
Grid.Column="2"
Orientation="Horizontal"
Visibility="Collapsed">
<Button
Width="48"
Height="48"
Margin="8,0,0,0"
Command="{Binding Path=DataContext.RemoveEntryCommand, Source={StaticResource BindingProxy}}"
CommandParameter="{Binding}"
Content="&#xE74D;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
ToolTipService.ToolTip="删除清单"/>
</StackPanel>
Margin="16,0,4,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{Binding CountFormatted}"/>
</Grid>
<ScrollViewer Grid.Row="1" Height="240">
<ItemsControl Margin="8,0,8,8" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<shvc:ItemIcon
Width="32"
Height="32"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<FontIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Glyph="&#xE73E;"
Visibility="{Binding IsFinished, Converter={StaticResource BoolToVisibilityConverter}}"/>
</Grid>
<Button
Grid.Column="1"
Height="32"
Margin="6,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding FinishStateCommand}">
<Grid Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}">
</DataTemplate>
</cwucont:AdaptiveGridView.ItemTemplate>
</cwucont:AdaptiveGridView>
</PivotItem>
<PivotItem Header="养成物品">
<cwucont:AdaptiveGridView
Padding="16,16,4,4"
cwua:ItemsReorderAnimation.Duration="0:0:0.1"
DesiredWidth="320"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemsSource="{Binding CultivateEntries}"
SelectionMode="None">
<cwucont:AdaptiveGridView.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource BorderCardStyle}">
<Grid Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<shvc:ItemIcon
Width="48"
Height="48"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
<TextBlock
Grid.Column="1"
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="{Binding Name}"/>
<StackPanel
x:Name="ButtonPanel"
Grid.Column="2"
Orientation="Horizontal"
Visibility="Collapsed">
<Button
Width="48"
Height="48"
Margin="8,0,0,0"
Command="{Binding Path=DataContext.RemoveEntryCommand, Source={StaticResource BindingProxy}}"
CommandParameter="{Binding}"
Content="&#xE74D;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
ToolTipService.ToolTip="删除清单"/>
</StackPanel>
</Grid>
<ScrollViewer Grid.Row="1" Height="240">
<ItemsControl Margin="8,0,8,8" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="0,0,0,0"
VerticalAlignment="Center"
Style="{Binding IsToday, Converter={StaticResource BoolToStyleSelector}}"
Text="{Binding Inner.Name}"
TextTrimming="CharacterEllipsis"/>
<TextBlock
<Grid>
<shvc:ItemIcon
Width="32"
Height="32"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<FontIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Glyph="&#xE73E;"
Visibility="{Binding IsFinished, Converter={StaticResource BoolToVisibilityConverter}}"/>
</Grid>
<Button
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Style="{Binding IsToday, Converter={StaticResource BoolToStyleSelector}}"
Text="{Binding Entity.Count}"/>
Height="32"
Margin="6,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding Path=DataContext.FinishStateCommand, Source={StaticResource BindingProxy}}"
CommandParameter="{Binding}">
<Grid Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="0,0,0,0"
VerticalAlignment="Center"
Style="{Binding IsToday, Converter={StaticResource BoolToStyleSelector}}"
Text="{Binding Inner.Name}"
TextTrimming="CharacterEllipsis"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Style="{Binding IsToday, Converter={StaticResource BoolToStyleSelector}}"
Text="{Binding Entity.Count}"/>
</Grid>
</Button>
</Grid>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Grid.Resources>
<Storyboard x:Name="ButtonPanelVisibleStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonPanel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Grid.Resources>
<Storyboard x:Name="ButtonPanelVisibleStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonPanel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="ButtonPanelCollapsedStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonPanel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
<Storyboard x:Name="ButtonPanelCollapsedStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonPanel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="PointerEntered">
<mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelVisibleStoryboard}"/>
</mxic:EventTriggerBehavior>
<mxic:EventTriggerBehavior EventName="PointerExited">
<mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelCollapsedStoryboard}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</Grid>
</Border>
</DataTemplate>
</cwucont:AdaptiveGridView.ItemTemplate>
</cwucont:AdaptiveGridView>
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="PointerEntered">
<mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelVisibleStoryboard}"/>
</mxic:EventTriggerBehavior>
<mxic:EventTriggerBehavior EventName="PointerExited">
<mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelCollapsedStoryboard}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</Grid>
</Border>
</DataTemplate>
</cwucont:AdaptiveGridView.ItemTemplate>
</cwucont:AdaptiveGridView>
</PivotItem>
</Pivot>
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
@@ -305,7 +304,6 @@
</wsc:SettingsGroup>
</StackPanel>
</Grid>
</PivotItem>
<PivotItem Header="背包物品">
<cwucont:AdaptiveGridView

View File

@@ -24,11 +24,13 @@ internal class CultivationViewModel : Abstraction.ViewModel
private readonly IMetadataService metadataService;
private readonly ILogger<CultivationViewModel> logger;
private readonly ConcurrentCancellationTokenSource statisticsCancellationTokenSource = new();
private ObservableCollection<CultivateProject>? projects;
private CultivateProject? selectedProject;
private List<Model.Binding.Inventory.InventoryItem>? inventoryItems;
private ObservableCollection<Model.Binding.Cultivation.CultivateEntry>? cultivateEntries;
private List<StatisticsCultivateItem>? statisticsItems;
private ObservableCollection<StatisticsCultivateItem>? statisticsItems;
/// <summary>
/// 构造一个新的养成视图模型
@@ -53,8 +55,8 @@ internal class CultivationViewModel : Abstraction.ViewModel
RemoveProjectCommand = new AsyncRelayCommand<CultivateProject>(RemoveProjectAsync);
RemoveEntryCommand = new AsyncRelayCommand<Model.Binding.Cultivation.CultivateEntry>(RemoveEntryAsync);
SaveInventoryItemCommand = new RelayCommand<Model.Binding.Inventory.InventoryItem>(SaveInventoryItem);
UpdateStatisticsItemsCommand = new AsyncRelayCommand(UpdateStatisticsItemsAsync);
NavigateToPageCommand = new RelayCommand<string>(NavigateToPage);
FinishStateCommand = new RelayCommand<Model.Binding.Cultivation.CultivateItem>(FlipFinishedState);
}
/// <summary>
@@ -74,7 +76,7 @@ internal class CultivationViewModel : Abstraction.ViewModel
cultivationService.Current = value;
if (value != null)
{
UpdateCultivateEntriesAndInventoryItemsAsync(value).SafeForget(logger);
UpdateEntryCollectionAsync(value).SafeForget(logger);
}
}
}
@@ -93,7 +95,7 @@ internal class CultivationViewModel : Abstraction.ViewModel
/// <summary>
/// 统计列表
/// </summary>
public List<StatisticsCultivateItem>? StatisticsItems { get => statisticsItems; set => SetProperty(ref statisticsItems, value); }
public ObservableCollection<StatisticsCultivateItem>? StatisticsItems { get => statisticsItems; set => SetProperty(ref statisticsItems, value); }
/// <summary>
/// 打开界面命令
@@ -120,16 +122,16 @@ internal class CultivationViewModel : Abstraction.ViewModel
/// </summary>
public ICommand SaveInventoryItemCommand { get; }
/// <summary>
/// 展示统计物品命令
/// </summary>
public ICommand UpdateStatisticsItemsCommand { get; }
/// <summary>
/// 导航到指定的页面命令
/// </summary>
public ICommand NavigateToPageCommand { get; set; }
/// <summary>
/// 调整完成状态命令
/// </summary>
public ICommand FinishStateCommand { get; }
private async Task OpenUIAsync()
{
bool metaInitialized = await metadataService.InitializeAsync().ConfigureAwait(true);
@@ -137,7 +139,6 @@ internal class CultivationViewModel : Abstraction.ViewModel
{
Projects = cultivationService.GetProjectCollection();
SelectedProject = cultivationService.Current;
await UpdateCultivateEntriesAndInventoryItemsAsync(SelectedProject).ConfigureAwait(true);
}
IsInitialized = metaInitialized;
@@ -184,7 +185,7 @@ internal class CultivationViewModel : Abstraction.ViewModel
}
}
private async Task UpdateCultivateEntriesAndInventoryItemsAsync(CultivateProject? project)
private async Task UpdateEntryCollectionAsync(CultivateProject? project)
{
if (project != null)
{
@@ -193,24 +194,25 @@ internal class CultivationViewModel : Abstraction.ViewModel
Dictionary<Model.Primitive.WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
ObservableCollection<Model.Binding.Cultivation.CultivateEntry> entries = await cultivationService
.GetCultivateEntriesAsync(project, materials, idAvatarMap, idWeaponMap)
.GetCultivateEntriesAsync(project)
.ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
CultivateEntries = entries;
InventoryItems = cultivationService.GetInventoryItems(project, materials);
await UpdateStatisticsItemsAsync().ConfigureAwait(false);
}
}
private Task RemoveEntryAsync(Model.Binding.Cultivation.CultivateEntry? entry)
private async Task RemoveEntryAsync(Model.Binding.Cultivation.CultivateEntry? entry)
{
if (entry != null)
{
CultivateEntries!.Remove(entry);
return cultivationService.RemoveCultivateEntryAsync(entry.EntryId);
await cultivationService.RemoveCultivateEntryAsync(entry.EntryId).ConfigureAwait(false);
await UpdateStatisticsItemsAsync().ConfigureAwait(false);
}
return Task.CompletedTask;
}
private void SaveInventoryItem(Model.Binding.Inventory.InventoryItem? inventoryItem)
@@ -218,20 +220,39 @@ internal class CultivationViewModel : Abstraction.ViewModel
if (inventoryItem != null)
{
cultivationService.SaveInventoryItem(inventoryItem);
UpdateStatisticsItemsAsync().SafeForget();
}
}
private void FlipFinishedState(Model.Binding.Cultivation.CultivateItem? item)
{
if (item != null)
{
item.IsFinished = !item.IsFinished;
cultivationService.SaveCultivateItem(item.Entity);
UpdateStatisticsItemsAsync().SafeForget();
}
}
private async Task UpdateStatisticsItemsAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(true))
logger.LogInformation("UpdateStatisticsItemsAsync");
if (SelectedProject != null)
{
if (SelectedProject != null)
await ThreadHelper.SwitchToBackgroundAsync();
CancellationToken token = statisticsCancellationTokenSource.Register();
ObservableCollection<StatisticsCultivateItem> statistics;
try
{
List<Model.Metadata.Material> materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false);
List<StatisticsCultivateItem> temp = await cultivationService.GetStatisticsCultivateItemsAsync(SelectedProject, materials).ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
StatisticsItems = temp;
statistics = await cultivationService.GetStatisticsCultivateItemCollectionAsync(SelectedProject, token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return;
}
await ThreadHelper.SwitchToMainThreadAsync();
StatisticsItems = statistics;
}
}

View File

@@ -77,7 +77,7 @@ internal class GachaLogViewModel : Abstraction.ViewModel
public GachaArchive? SelectedArchive
{
get => selectedArchive;
set => SetSelectedArchiveAndUpdateStatistics(value, false);
set => SetSelectedArchiveAndUpdateStatistics(value);
}
/// <summary>
@@ -158,7 +158,6 @@ internal class GachaLogViewModel : Abstraction.ViewModel
await ThreadHelper.SwitchToMainThreadAsync();
Archives = archives;
SelectedArchive = Archives.SingleOrDefault(a => a.IsSelected == true);
IsInitialized = true;
}
}
catch (OperationCanceledException)
@@ -323,9 +322,14 @@ internal class GachaLogViewModel : Abstraction.ViewModel
OnPropertyChanged(nameof(SelectedArchive));
}
if (changed || forceUpdate)
if (forceUpdate || changed)
{
if (archive != null)
if (archive == null)
{
// no gachalog
IsInitialized = true;
}
else
{
UpdateStatisticsAsync(archive).SafeForget();
}
@@ -337,6 +341,7 @@ internal class GachaLogViewModel : Abstraction.ViewModel
GachaStatistics temp = await gachaLogService.GetStatisticsAsync(archive).ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
Statistics = temp;
IsInitialized = true;
}
private async Task<bool> TryImportUIGFInternalAsync(UIGF uigf)

View File

@@ -230,9 +230,18 @@ public class MiHoYoJSInterface
/// </summary>
/// <param name="param">参数</param>
/// <returns>响应</returns>
public virtual IJsResult? ClosePage(JsParam param)
public virtual async Task<IJsResult?> ClosePageAsync(JsParam param)
{
ClosePageRequested?.Invoke();
await ThreadHelper.SwitchToMainThreadAsync();
if (webView.CanGoBack)
{
webView.GoBack();
}
else
{
ClosePageRequested?.Invoke();
}
return null;
}
@@ -357,7 +366,7 @@ public class MiHoYoJSInterface
{
return param.Method switch
{
"closePage" => ClosePage(param),
"closePage" => await ClosePageAsync(param).ConfigureAwait(false),
"configure_share" => ConfigureShare(param),
"eventTrack" => null,
"getActionTicket" => await GetActionTicketAsync(param).ConfigureAwait(false),