make DI initialize in program
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
@@ -11,57 +9,31 @@ using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using System.Diagnostics;
|
||||
using Windows.Storage;
|
||||
using Windows.UI.ViewManagement;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton)]
|
||||
public partial class App : Application
|
||||
{
|
||||
private static Window? window;
|
||||
private readonly ILogger<App> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object.
|
||||
/// </summary>
|
||||
public App()
|
||||
/// <param name="logger">日志器</param>
|
||||
public App(ILogger<App> logger)
|
||||
{
|
||||
// load app resource
|
||||
InitializeComponent();
|
||||
InitializeDependencyInjection();
|
||||
|
||||
// Notice that we already call InitializeDependencyInjection() above
|
||||
// so we can use Ioc here.
|
||||
logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
||||
this.logger = logger;
|
||||
|
||||
_ = new ExceptionRecorder(this, logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前窗口
|
||||
/// </summary>
|
||||
public static Window? Window { get => window; set => window = value; }
|
||||
|
||||
/// <inheritdoc cref="Application"/>
|
||||
public static new App Current
|
||||
{
|
||||
get => (App)Application.Current;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ApplicationData.Current.TemporaryFolder"/>
|
||||
public static StorageFolder CacheFolder
|
||||
{
|
||||
get => ApplicationData.Current.TemporaryFolder;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ApplicationData.Current.LocalSettings"/>
|
||||
public static ApplicationDataContainer Settings
|
||||
{
|
||||
get => ApplicationData.Current.LocalSettings;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[SuppressMessage("", "VSTHRD100")]
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
@@ -71,11 +43,11 @@ public partial class App : Application
|
||||
|
||||
if (firstInstance.IsCurrent)
|
||||
{
|
||||
// manually invoke the
|
||||
// manually invoke
|
||||
Activation.Activate(firstInstance, activatedEventArgs);
|
||||
firstInstance.Activated += Activation.Activate;
|
||||
|
||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", CacheFolder.Path);
|
||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", ApplicationData.Current.TemporaryFolder.Path);
|
||||
|
||||
Ioc.Default
|
||||
.GetRequiredService<IMetadataService>()
|
||||
@@ -90,29 +62,4 @@ public partial class App : Application
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializeDependencyInjection()
|
||||
{
|
||||
IServiceProvider services = new ServiceCollection()
|
||||
|
||||
// Microsoft extension
|
||||
.AddLogging(builder => builder
|
||||
.AddDebug()
|
||||
.AddDatabase())
|
||||
.AddMemoryCache()
|
||||
|
||||
// Hutao extensions
|
||||
.AddJsonSerializerOptions()
|
||||
.AddDatebase()
|
||||
.AddInjections()
|
||||
.AddHttpClients()
|
||||
|
||||
// Discrete services
|
||||
.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
|
||||
.AddSingleton(new UISettings())
|
||||
|
||||
.BuildServiceProvider();
|
||||
|
||||
Ioc.Default.ConfigureServices(services);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 432 B After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 637 B After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 283 B After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 87 KiB |
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Extension;
|
||||
|
||||
namespace Snap.Hutao.Control.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// 对话框扩展
|
||||
/// </summary>
|
||||
internal static class ContentDialogExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 阻止用户交互
|
||||
/// </summary>
|
||||
/// <param name="contentDialog">对话框</param>
|
||||
/// <returns>用于恢复用户交互</returns>
|
||||
public static IDisposable BlockInteraction(this ContentDialog contentDialog)
|
||||
{
|
||||
contentDialog.ShowAsync().AsTask().SafeForget();
|
||||
return new ContentDialogHider(contentDialog);
|
||||
}
|
||||
|
||||
private struct ContentDialogHider : IDisposable
|
||||
{
|
||||
private readonly ContentDialog contentDialog;
|
||||
|
||||
public ContentDialogHider(ContentDialog contentDialog)
|
||||
{
|
||||
this.contentDialog = contentDialog;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
contentDialog.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using Microsoft.UI.Xaml.Hosting;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.Exception;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
@@ -132,13 +131,8 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
imageSurface = await LoadImageSurfaceAsync(storageFile, token);
|
||||
}
|
||||
catch (COMException ex) when (ex.Is(COMError.STG_E_FILENOTFOUND))
|
||||
catch (COMException)
|
||||
{
|
||||
// Image file not found.
|
||||
}
|
||||
catch (COMException ex) when (ex.Is(COMError.WINCODEC_ERR_COMPONENTNOTFOUND))
|
||||
{
|
||||
// Image is broken, remove it
|
||||
await imageCache.RemoveAsync(uri.Enumerate());
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public class MonoChrome : CompositionImage
|
||||
{
|
||||
ElementTheme.Light => ApplicationTheme.Light,
|
||||
ElementTheme.Dark => ApplicationTheme.Dark,
|
||||
_ => App.Current.RequestedTheme,
|
||||
_ => Ioc.Default.GetRequiredService<App>().RequestedTheme,
|
||||
};
|
||||
|
||||
backgroundBrush.Color = theme switch
|
||||
|
||||
@@ -216,7 +216,7 @@ public abstract class CacheBase<T>
|
||||
|
||||
using (await cacheFolderSemaphore.EnterAsync().ConfigureAwait(false))
|
||||
{
|
||||
baseFolder ??= App.CacheFolder;
|
||||
baseFolder ??= ApplicationData.Current.TemporaryFolder;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cacheFolderName))
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ internal static class Activation
|
||||
/// <param name="args">激活参数</param>
|
||||
public static void Activate(object? sender, AppActivationArguments args)
|
||||
{
|
||||
_ = sender;
|
||||
HandleActivationAsync(args).SafeForget();
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ internal static class Activation
|
||||
|
||||
private static async Task HandleActivationCoreAsync(AppActivationArguments args)
|
||||
{
|
||||
App.Window = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
_ = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
await infoBarService.WaitInitializationAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -18,7 +18,7 @@ internal static class LocalSetting
|
||||
|
||||
static LocalSetting()
|
||||
{
|
||||
Container = App.Settings;
|
||||
Container = ApplicationData.Current.LocalSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -28,7 +28,7 @@ internal static class LocalSetting
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>获取的值</returns>
|
||||
[return:MaybeNull]
|
||||
[return: MaybeNull]
|
||||
public static T Get<T>(string key, [AllowNull] T defaultValue = default)
|
||||
{
|
||||
if (Container.Values.TryGetValue(key, out object? value))
|
||||
|
||||
@@ -57,4 +57,20 @@ public static class ThemeHelper
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否为暗黑模式
|
||||
/// </summary>
|
||||
/// <param name="elementTheme">当前元素主题</param>
|
||||
/// <param name="applicationTheme">当前应用主题</param>
|
||||
/// <returns>是否为暗黑模式</returns>
|
||||
public static bool IsDarkMode(ElementTheme elementTheme, ApplicationTheme applicationTheme)
|
||||
{
|
||||
return elementTheme switch
|
||||
{
|
||||
ElementTheme.Default => applicationTheme == ApplicationTheme.Dark,
|
||||
ElementTheme.Dark => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,10 @@ public struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOpe
|
||||
/// <summary>
|
||||
/// 是否完成
|
||||
/// </summary>
|
||||
public bool IsCompleted => dispatherQueue.HasThreadAccess;
|
||||
public bool IsCompleted
|
||||
{
|
||||
get => dispatherQueue.HasThreadAccess;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnCompleted(Action continuation)
|
||||
|
||||
@@ -116,10 +116,12 @@ internal sealed class WindowManager : IDisposable
|
||||
appTitleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
appTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||
|
||||
Color systemBaseLowColor = (Color)App.Current.Resources["SystemBaseLowColor"];
|
||||
App app = Ioc.Default.GetRequiredService<App>();
|
||||
|
||||
Color systemBaseLowColor = (Color)app.Resources["SystemBaseLowColor"];
|
||||
appTitleBar.ButtonHoverBackgroundColor = systemBaseLowColor;
|
||||
|
||||
Color systemBaseMediumLowColor = (Color)App.Current.Resources["SystemBaseMediumLowColor"];
|
||||
Color systemBaseMediumLowColor = (Color)app.Resources["SystemBaseMediumLowColor"];
|
||||
appTitleBar.ButtonPressedBackgroundColor = systemBaseMediumLowColor;
|
||||
|
||||
// The Foreground doesn't accept Alpha channel. So we translate it to gray.
|
||||
@@ -127,7 +129,7 @@ internal sealed class WindowManager : IDisposable
|
||||
byte result = (byte)((systemBaseMediumLowColor.A / 255.0) * light);
|
||||
appTitleBar.ButtonInactiveForegroundColor = Color.FromArgb(0xFF, result, result, result);
|
||||
|
||||
Color systemBaseHighColor = (Color)App.Current.Resources["SystemBaseHighColor"];
|
||||
Color systemBaseHighColor = (Color)app.Resources["SystemBaseHighColor"];
|
||||
appTitleBar.ButtonForegroundColor = systemBaseHighColor;
|
||||
appTitleBar.ButtonHoverForegroundColor = systemBaseHighColor;
|
||||
appTitleBar.ButtonPressedForegroundColor = systemBaseHighColor;
|
||||
|
||||
@@ -194,6 +194,8 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 草元素伤害加成
|
||||
/// </summary>
|
||||
[Description("草元素伤害加成")]
|
||||
[Format(FormatMethod.Percent)]
|
||||
FIGHT_PROP_GRASS_ADD_HURT = 43,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
/// </summary>
|
||||
internal class AvatarIconConverter : IValueConverter
|
||||
{
|
||||
private const string BaseUrl = "https://upload-bbs.mihoyo.com/game_record/genshin/character_icon/{0}.png";
|
||||
private const string BaseUrl = "https://static.snapgenshin.com/AvatarIcon/{0}.png";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
/// </summary>
|
||||
internal class AvatarSideIconConverter : IValueConverter
|
||||
{
|
||||
private const string BaseUrl = "https://upload-bbs.mihoyo.com/game_record/genshin/character_side_icon/{0}.png";
|
||||
private const string BaseUrl = "https://static.snapgenshin.com/AvatarIcon/{0}.png";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.UI.ViewManagement;
|
||||
using WinRT;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
@@ -35,12 +39,44 @@ public static class Program
|
||||
XamlCheckProcessRequirements();
|
||||
ComWrappersSupport.InitializeComWrappers();
|
||||
|
||||
Application.Start(p =>
|
||||
{
|
||||
dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
DispatcherQueueSynchronizationContext context = new(dispatcherQueue);
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
_ = new App();
|
||||
});
|
||||
InitializeDependencyInjection();
|
||||
|
||||
// In a Desktop app this runs a message pump internally,
|
||||
// and does not return until the application shuts down.
|
||||
Application.Start(InitializeApp);
|
||||
}
|
||||
|
||||
private static void InitializeApp(ApplicationInitializationCallbackParams param)
|
||||
{
|
||||
dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
DispatcherQueueSynchronizationContext context = new(dispatcherQueue);
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
|
||||
_ = Ioc.Default.GetRequiredService<App>();
|
||||
}
|
||||
|
||||
private static void InitializeDependencyInjection()
|
||||
{
|
||||
IServiceProvider services = new ServiceCollection()
|
||||
|
||||
// Microsoft extension
|
||||
.AddLogging(builder => builder
|
||||
.AddDebug()
|
||||
.AddDatabase())
|
||||
.AddMemoryCache()
|
||||
|
||||
// Hutao extensions
|
||||
.AddJsonSerializerOptions()
|
||||
.AddDatebase()
|
||||
.AddInjections()
|
||||
.AddHttpClients()
|
||||
|
||||
// Discrete services
|
||||
.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
|
||||
.AddSingleton(new UISettings())
|
||||
|
||||
.BuildServiceProvider();
|
||||
|
||||
Ioc.Default.ConfigureServices(services);
|
||||
}
|
||||
}
|
||||
@@ -151,6 +151,12 @@ internal class AchievementService : IAchievementService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<ImportResult> ImportFromUIAFAsync(EntityArchive archive, List<UIAFItem> list, ImportOption option)
|
||||
{
|
||||
return Task.Run(() => ImportFromUIAF(archive, list, option));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SaveAchievements(EntityArchive archive, IList<BindingAchievement> achievements)
|
||||
{
|
||||
|
||||
@@ -40,9 +40,18 @@ internal interface IAchievementService
|
||||
/// <param name="archive">用户</param>
|
||||
/// <param name="list">UIAF数据</param>
|
||||
/// <param name="option">选项</param>
|
||||
/// <returns>导入</returns>
|
||||
/// <returns>导入结果</returns>
|
||||
ImportResult ImportFromUIAF(EntityArchive archive, List<UIAFItem> list, ImportOption option);
|
||||
|
||||
/// <summary>
|
||||
/// 异步导入UIAF数据
|
||||
/// </summary>
|
||||
/// <param name="archive">用户</param>
|
||||
/// <param name="list">UIAF数据</param>
|
||||
/// <param name="option">选项</param>
|
||||
/// <returns>导入结果</returns>
|
||||
Task<ImportResult> ImportFromUIAFAsync(EntityArchive archive, List<UIAFItem> list, ImportOption option);
|
||||
|
||||
/// <summary>
|
||||
/// 异步移除存档
|
||||
/// </summary>
|
||||
|
||||
@@ -35,4 +35,10 @@ public struct ImportResult
|
||||
Update = update;
|
||||
Remove = remove;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"新增:{Add} 个成就 | 更新:{Update} 个成就 | 删除{Remove} 个成就";
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
<StackPanel
|
||||
x:Name="InfoBarStack"
|
||||
Margin="32"
|
||||
Margin="32,48,32,32"
|
||||
MaxWidth="640"
|
||||
VerticalAlignment="Bottom">
|
||||
<StackPanel.Resources>
|
||||
|
||||
@@ -50,13 +50,15 @@ public sealed partial class MainView : UserControl
|
||||
{
|
||||
await Program.SwitchToMainThreadAsync();
|
||||
|
||||
if (!ThemeHelper.Equals(App.Current.RequestedTheme, RequestedTheme))
|
||||
App current = Ioc.Default.GetRequiredService<App>();
|
||||
|
||||
if (!ThemeHelper.Equals(current.RequestedTheme, RequestedTheme))
|
||||
{
|
||||
ILogger<MainView> logger = Ioc.Default.GetRequiredService<ILogger<MainView>>();
|
||||
logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] App Theme [{app}]", RequestedTheme, App.Current.RequestedTheme);
|
||||
logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] App Theme [{app}]", RequestedTheme, current.RequestedTheme);
|
||||
|
||||
// Update controls' theme which presents in the PopupRoot
|
||||
RequestedTheme = ThemeHelper.ApplicationToElement(App.Current.RequestedTheme);
|
||||
RequestedTheme = ThemeHelper.ApplicationToElement(current.RequestedTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Windows.System;
|
||||
@@ -21,7 +22,8 @@ public sealed partial class AnnouncementContentPage : Microsoft.UI.Xaml.Controls
|
||||
private const string LightColor3 = "color:rgba(204,204,204,1)";
|
||||
private const string LightColor4 = "color:rgba(198,196,191,1)";
|
||||
private const string LightColor5 = "color:rgba(170,170,170,1)";
|
||||
private const string LightAccentColor = "background-color: rgb(0,40,70)";
|
||||
private const string LightAccentColor1 = "background-color: rgb(0,40,70)";
|
||||
private const string LightAccentColor2 = "background-color: rgb(1,40,70)";
|
||||
|
||||
// find in content
|
||||
private const string DarkColor1 = "color:rgba(0,0,0,1)";
|
||||
@@ -30,6 +32,7 @@ public sealed partial class AnnouncementContentPage : Microsoft.UI.Xaml.Controls
|
||||
private const string DarkColor4 = "color:rgba(57,59,64,1)";
|
||||
private const string DarkColor5 = "color:rgba(85,85,85,1)";
|
||||
private const string DarkAccentColor1 = "background-color: rgb(255, 215, 185);";
|
||||
private const string DarkAccentColor2 = "background-color: rgb(254, 245, 231);";
|
||||
|
||||
// support click open browser.
|
||||
private const string MihoyoSDKDefinition =
|
||||
@@ -66,25 +69,19 @@ openInWebview: function(url){ location.href = url }}";
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
bool isDarkMode = theme switch
|
||||
{
|
||||
ElementTheme.Default => App.Current.RequestedTheme == ApplicationTheme.Dark,
|
||||
ElementTheme.Dark => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (isDarkMode)
|
||||
if (ThemeHelper.IsDarkMode(theme, Ioc.Default.GetRequiredService<App>().RequestedTheme))
|
||||
{
|
||||
rawContent = rawContent
|
||||
.Replace(DarkColor5, LightColor5)
|
||||
.Replace(DarkColor4, LightColor4)
|
||||
.Replace(DarkColor3, LightColor3)
|
||||
.Replace(DarkColor2, LightColor2)
|
||||
.Replace(DarkAccentColor1, LightAccentColor);
|
||||
.Replace(DarkAccentColor1, LightAccentColor1)
|
||||
.Replace(DarkAccentColor2, LightAccentColor2);
|
||||
}
|
||||
|
||||
// wrap a default color body around
|
||||
return $@"<body style=""{(isDarkMode ? LightColor1 : DarkColor1)}"">{rawContent}</body>";
|
||||
return $@"<body style=""{(ThemeHelper.IsDarkMode(theme, Ioc.Default.GetRequiredService<App>().RequestedTheme) ? LightColor1 : DarkColor1)}"">{rawContent}</body>";
|
||||
}
|
||||
|
||||
private async Task LoadAnnouncementAsync(INavigationData data)
|
||||
|
||||
@@ -25,9 +25,7 @@
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="检查更新"
|
||||
Description="根本没有检查更新选项">
|
||||
|
||||
</sc:Setting>
|
||||
Description="根本没有检查更新选项"/>
|
||||
</sc:SettingExpander.Header>
|
||||
<InfoBar
|
||||
IsClosable="False"
|
||||
@@ -60,7 +58,7 @@
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="米游社签到"
|
||||
Description="所有账号的所有角色都会签到,每次间隔 15s">
|
||||
Description="所有账号的所有角色都会签到,每次间隔 5-15s">
|
||||
<sc:Setting.ActionContent>
|
||||
<Button Content="签到" Command="{Binding Experimental.SignAllUserGameRolesCommand}"/>
|
||||
</sc:Setting.ActionContent>
|
||||
|
||||
@@ -7,6 +7,7 @@ using CommunityToolkit.WinUI.UI;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Control.Cancellable;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Message;
|
||||
@@ -21,6 +22,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Pickers;
|
||||
@@ -211,7 +213,7 @@ internal class AchievementViewModel
|
||||
|
||||
private static Task<ContentDialogResult> ShowImportFailDialogAsync(string message)
|
||||
{
|
||||
return new ContentDialog2(App.Window!)
|
||||
return new ContentDialog2(Ioc.Default.GetRequiredService<MainWindow>())
|
||||
{
|
||||
Title = "导入失败",
|
||||
Content = message,
|
||||
@@ -239,7 +241,8 @@ internal class AchievementViewModel
|
||||
|
||||
if (metaInitialized)
|
||||
{
|
||||
AchievementGoals = await metadataService.GetAchievementGoalsAsync(CancellationToken);
|
||||
List<AchievementGoal> goals = await metadataService.GetAchievementGoalsAsync(CancellationToken);
|
||||
AchievementGoals = goals.OrderBy(goal => goal.Order).ToList();
|
||||
|
||||
Archives = achievementService.GetArchiveCollection();
|
||||
|
||||
@@ -265,7 +268,8 @@ internal class AchievementViewModel
|
||||
|
||||
private async Task AddArchiveAsync()
|
||||
{
|
||||
(bool isOk, string name) = await new AchievementArchiveCreateDialog(App.Window!).GetInputAsync();
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
(bool isOk, string name) = await new AchievementArchiveCreateDialog(mainWindow).GetInputAsync();
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
@@ -292,7 +296,8 @@ internal class AchievementViewModel
|
||||
{
|
||||
if (Archives != null && SelectedArchive != null)
|
||||
{
|
||||
ContentDialogResult result = await new ContentDialog2(App.Window!)
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
ContentDialogResult result = await new ContentDialog2(mainWindow)
|
||||
{
|
||||
Title = $"确定要删除存档 {SelectedArchive.Name} 吗?",
|
||||
Content = "该操作是不可逆的,该存档和其内的所有成就状态会丢失。",
|
||||
@@ -321,25 +326,9 @@ internal class AchievementViewModel
|
||||
return;
|
||||
}
|
||||
|
||||
string json = await Clipboard.GetContent().GetTextAsync();
|
||||
|
||||
if (GetUIAFFromString(json) is UIAF uiaf)
|
||||
if (await GetUIAFFromClipboardAsync() is UIAF uiaf)
|
||||
{
|
||||
if (uiaf.IsCurrentVersionSupported())
|
||||
{
|
||||
(bool isOk, ImportOption option) = await new AchievementImportDialog(App.Window!, uiaf).GetImportOptionAsync();
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ImportResult result = achievementService.ImportFromUIAF(achievementService.CurrentArchive, uiaf.List, option);
|
||||
infoBarService!.Success($"新增:{result.Add} 个成就 | 更新:{result.Update} 个成就 | 删除{result.Remove} 个成就");
|
||||
await UpdateAchievementsAsync(achievementService.CurrentArchive);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ShowImportFailDialogAsync("数据的 UIAF 版本过低,无法导入");
|
||||
}
|
||||
await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -363,21 +352,7 @@ internal class AchievementViewModel
|
||||
{
|
||||
if (await GetUIAFFromFileAsync(file) is UIAF uiaf)
|
||||
{
|
||||
if (uiaf.IsCurrentVersionSupported())
|
||||
{
|
||||
(bool isOk, ImportOption option) = await new AchievementImportDialog(App.Window!, uiaf).GetImportOptionAsync();
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ImportResult result = achievementService.ImportFromUIAF(achievementService.CurrentArchive, uiaf.List, option);
|
||||
infoBarService!.Success($"新增:{result.Add} 个成就 | 更新:{result.Update} 个成就 | 删除{result.Remove} 个成就");
|
||||
await UpdateAchievementsAsync(achievementService.CurrentArchive);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ShowImportFailDialogAsync("数据的 UIAF 版本过低,无法导入");
|
||||
}
|
||||
await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -386,9 +361,20 @@ internal class AchievementViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private UIAF? GetUIAFFromString(string json)
|
||||
private async Task<UIAF?> GetUIAFFromClipboardAsync()
|
||||
{
|
||||
UIAF? uiaf = null;
|
||||
string json;
|
||||
try
|
||||
{
|
||||
json = await Clipboard.GetContent().GetTextAsync();
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
infoBarService?.Error(ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
uiaf = JsonSerializer.Deserialize<UIAF>(json, options);
|
||||
@@ -422,6 +408,41 @@ internal class AchievementViewModel
|
||||
return uiaf;
|
||||
}
|
||||
|
||||
private async Task<bool> TryImportUIAFInternalAsync(Model.Entity.AchievementArchive archive, UIAF uiaf)
|
||||
{
|
||||
if (uiaf.IsCurrentVersionSupported())
|
||||
{
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
(bool isOk, ImportOption option) = await new AchievementImportDialog(mainWindow, uiaf).GetImportOptionAsync();
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ContentDialog2 importingDialog = new(Ioc.Default.GetRequiredService<MainWindow>())
|
||||
{
|
||||
Title = "导入成就中",
|
||||
Content = new ProgressBar() { IsIndeterminate = true },
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
};
|
||||
|
||||
ImportResult result;
|
||||
using (importingDialog.BlockInteraction())
|
||||
{
|
||||
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, option);
|
||||
}
|
||||
|
||||
infoBarService.Success(result.ToString());
|
||||
await UpdateAchievementsAsync(archive);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ShowImportFailDialogAsync("数据的 UIAF 版本过低,无法导入");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdateAchievementFilter(AchievementGoal? goal)
|
||||
{
|
||||
if (Achievements != null)
|
||||
|
||||
@@ -7,6 +7,7 @@ using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Sign;
|
||||
using System.Text;
|
||||
using Windows.Storage;
|
||||
using Windows.System;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
@@ -60,7 +61,7 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
|
||||
|
||||
private Task OpenCacheFolderAsync()
|
||||
{
|
||||
return Launcher.LaunchFolderAsync(App.CacheFolder).AsTask();
|
||||
return Launcher.LaunchFolderAsync(ApplicationData.Current.TemporaryFolder).AsTask();
|
||||
}
|
||||
|
||||
private Task OpenDataFolderAsync()
|
||||
|
||||
@@ -121,7 +121,8 @@ internal class UserViewModel : ObservableObject
|
||||
private async Task AddUserAsync()
|
||||
{
|
||||
// Get cookie from user input
|
||||
(bool isOk, string cookie) = await new UserDialog(App.Window!).GetInputCookieAsync();
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
(bool isOk, string cookie) = await new UserDialog(mainWindow).GetInputCookieAsync();
|
||||
|
||||
// User confirms the input
|
||||
if (isOk)
|
||||
|
||||
@@ -161,7 +161,7 @@ public class Announcement : AnnouncementContent
|
||||
/// 提醒版本
|
||||
/// </summary>
|
||||
[JsonPropertyName("remind_ver")]
|
||||
public int RemindVer { get; set; }
|
||||
public int RemindVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否含有内容
|
||||
|
||||