Mirror酱集成 (#1666)

* 更多更新渠道的UI改造

* CDK存储逻辑

* feat: enhance CDK input dialog with left button and update check logic for Alpha channel

* 支持修改cdk

* 支持删除cdk
This commit is contained in:
辉鸭蛋
2025-06-02 00:03:44 +08:00
committed by GitHub
parent 468a54e037
commit e4e6953949
14 changed files with 701 additions and 80 deletions

View File

@@ -1,2 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=enkanomiya/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=chyan/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=enkanomiya/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mirrorchan/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=steambird/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -18,14 +18,10 @@
::-webkit-scrollbar-thumb:hover {
background: #555;
}
.body {
background-color: #202020;
color: #e6edf3;
}
.markdown-body {
margin: 0;
padding: 8px;
background-color: #202020;
background-color: #202020 !important;
height: 100%
}
* {

View File

@@ -48,6 +48,7 @@
<PackageReference Include="DeviceId.Windows" Version="6.9.0" />
<PackageReference Include="DeviceId.Windows.Wmi" Version="6.9.0" />
<PackageReference Include="Emoji.Wpf" Version="0.3.4" />
<PackageReference Include="Meziantou.Framework.Win32.CredentialManager" Version="1.7.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.4" />

View File

@@ -0,0 +1,52 @@
using System;
using Meziantou.Framework.Win32;
namespace BetterGenshinImpact.Helpers.Win32;
public static class CredentialManagerHelper
{
public static void SaveCredential(string applicationName, string userName, string secret, string comment,
CredentialPersistence persistence)
{
CredentialManager.WriteCredential(
applicationName: applicationName,
userName: userName,
secret: secret,
comment: comment,
persistence: persistence);
}
public static Credential? ReadCredential(string applicationName)
{
var credential = CredentialManager.ReadCredential(applicationName);
if (credential == null)
{
Console.WriteLine("No credential found.");
return null;
}
Console.WriteLine($"UserName: {credential.UserName}");
Console.WriteLine($"Secret: {credential.Password}");
Console.WriteLine($"Comment: {credential.Comment}");
return credential;
}
public static void UpdateCredential(string applicationName, string newUserName, string newSecret, string newComment)
{
SaveCredential(applicationName, newUserName, newSecret, newComment, CredentialPersistence.LocalMachine);
}
public static void DeleteCredential(string applicationName)
{
try
{
CredentialManager.DeleteCredential(applicationName);
Console.WriteLine("Credential deleted successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Error deleting credential: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,93 @@
using System;
using Windows.System;
using BetterGenshinImpact.View.Windows;
using Meziantou.Framework.Win32;
using Wpf.Ui.Violeta.Controls;
namespace BetterGenshinImpact.Helpers.Win32;
public static class MirrorChyanHelper
{
public static readonly string MirrorChyanCdkAppName = "KachinaInstaller_MirrorChyanCDK_BetterGI";
public static string? GetCdk()
{
var credential = CredentialManagerHelper.ReadCredential(MirrorChyanCdkAppName);
return credential?.Password;
}
public static string? GetAndPromptCdk()
{
var credential = CredentialManagerHelper.ReadCredential(MirrorChyanCdkAppName);
if (credential == null || credential.Password == null)
{
var cdk = PromptDialog.Prompt("Mirror酱是独立的第三方软件下载平台提供付费的软件下载加速服务。\n如果你有 Mirror酱的 CDK可以在这里输入。",
"请输入Mirror酱CDK",
string.Empty,
new PromptDialogConfig
{
ShowLeftButton = true,
LeftButtonText = "获取CDK",
LeftButtonClick = (sender, args) =>
{
Launcher.LaunchUriAsync(new Uri("https://mirrorchyan.com/zh/get-start"));
}
}
);
if (string.IsNullOrEmpty(cdk))
{
Toast.Warning("输入CDK为空无法继续操作");
return null;
}
CredentialManagerHelper.SaveCredential(
MirrorChyanCdkAppName,
string.Empty,
cdk,
string.Empty,
CredentialPersistence.LocalMachine);
return cdk;
}
else
{
return credential.Password;
}
}
public static void EditCdk()
{
var credential = CredentialManagerHelper.ReadCredential(MirrorChyanCdkAppName);
var cdk = PromptDialog.Prompt("Mirror酱是独立的第三方软件下载平台提供付费的软件下载加速服务。\n如果你有 Mirror酱的 CDK可以在这里输入。",
"修改Mirror酱CDK",
credential?.Password!,
new PromptDialogConfig
{
ShowLeftButton = true,
LeftButtonText = "获取CDK",
LeftButtonClick = (sender, args) =>
{
Launcher.LaunchUriAsync(new Uri("https://mirrorchyan.com/zh/get-start"));
}
}
);
if (string.IsNullOrEmpty(cdk))
{
DeleteCdk();
}
else
{
CredentialManagerHelper.SaveCredential(
MirrorChyanCdkAppName,
string.Empty,
cdk,
string.Empty,
CredentialPersistence.LocalMachine);
}
}
public static void DeleteCdk()
{
CredentialManagerHelper.DeleteCredential(MirrorChyanCdkAppName);
}
}

View File

@@ -3,6 +3,8 @@
public sealed class UpdateOption
{
public UpdateTrigger Trigger { get; set; } = default;
public UpdateChannel Channel { get; set; } = UpdateChannel.Stable;
}
public enum UpdateTrigger
@@ -10,3 +12,10 @@ public enum UpdateTrigger
Auto,
Manual,
}
public enum UpdateChannel
{
Stable,
Alpha,
}

View File

@@ -0,0 +1,117 @@
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.Service.Model.MirrorChyan;
#nullable enable
#pragma warning disable CS8618
#pragma warning disable CS8601
#pragma warning disable CS8603
public partial class LatestResponse
{
/// <summary>
/// 响应代码https://github.com/MirrorChyan/docs/blob/main/ErrorCode.md
/// </summary>
[JsonPropertyName("code")]
public long Code { get; set; }
/// <summary>
/// 响应数据
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("data")]
public Data Data { get; set; }
/// <summary>
/// 响应信息
/// </summary>
[JsonPropertyName("msg")]
public string Msg { get; set; }
}
/// <summary>
/// 响应数据
/// </summary>
public partial class Data
{
/// <summary>
/// 更新包架构
/// </summary>
[JsonPropertyName("arch")]
public string Arch { get; set; }
/// <summary>
/// CDK过期时间戳
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("cdk_expired_time")]
public double? CdkExpiredTime { get; set; }
/// <summary>
/// 更新频道stable | beta | alpha
/// </summary>
[JsonPropertyName("channel")]
public string Channel { get; set; }
/// <summary>
/// 自定义数据
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("custom_data")]
public string CustomData { get; set; }
/// <summary>
/// 文件大小
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("filesize")]
public long? Filesize { get; set; }
/// <summary>
/// 更新包系统
/// </summary>
[JsonPropertyName("os")]
public string Os { get; set; }
/// <summary>
/// 发版日志
/// </summary>
[JsonPropertyName("release_note")]
public string ReleaseNote { get; set; }
/// <summary>
/// sha256
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("sha256")]
public string Sha256 { get; set; }
/// <summary>
/// 更新包类型incremental | full
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("update_type")]
public string UpdateType { get; set; }
/// <summary>
/// 下载地址
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// 资源版本名称
/// </summary>
[JsonPropertyName("version_name")]
public string VersionName { get; set; }
/// <summary>
/// 资源版本号仅内部使用
/// </summary>
[JsonPropertyName("version_number")]
public long VersionNumber { get; set; }
}
#pragma warning restore CS8618
#pragma warning restore CS8601
#pragma warning restore CS8603

View File

@@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
@@ -18,6 +19,8 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using BetterGenshinImpact.Service.Model.MirrorChyan;
using Wpf.Ui.Violeta.Controls;
namespace BetterGenshinImpact.Service;
@@ -37,7 +40,7 @@ public class UpdateService : IUpdateService
_configService = configService;
Config = _configService.Get();
}
/// <summary>
/// Please call me in main thread
@@ -50,13 +53,13 @@ public class UpdateService : IUpdateService
#if DEBUG && false
return;
#endif
string newVersion = await GetLatestVersionAsync();
string newVersion = await GetLatestVersionAsync(option);
if (string.IsNullOrWhiteSpace(newVersion))
{
return;
}
// ---- 如果是调试模式且手动的检查更新的情况下,强制打开更新窗口 -----
// 方便调试窗口
if (RuntimeHelper.IsDebuggerAttached && option.Trigger == UpdateTrigger.Manual)
@@ -72,7 +75,7 @@ public class UpdateService : IUpdateService
{
await MessageBox.InformationAsync("当前已是最新版本!");
}
return;
}
@@ -87,7 +90,8 @@ public class UpdateService : IUpdateService
}
catch (Exception e)
{
Debug.WriteLine("获取最新版本信息失败:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message);
Debug.WriteLine("获取最新版本信息失败:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" +
Environment.NewLine + e.Message);
_logger.LogWarning("获取 BetterGI 最新版本信息失败");
}
}
@@ -110,7 +114,14 @@ public class UpdateService : IUpdateService
break;
case CheckUpdateWindow.CheckUpdateWindowButton.OtherUpdate:
Process.Start(new ProcessStartInfo(DownloadPageUrl) { UseShellExecute = true });
if (option.Channel == UpdateChannel.Stable)
{
Process.Start(new ProcessStartInfo(DownloadPageUrl) { UseShellExecute = true });
}
else
{
Process.Start(new ProcessStartInfo("https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml") { UseShellExecute = true });
}
break;
case CheckUpdateWindow.CheckUpdateWindowButton.Update:
@@ -122,12 +133,12 @@ public class UpdateService : IUpdateService
await MessageBox.ErrorAsync("更新程序不存在,请选择其他更新方式!");
return;
}
// 启动
Process.Start(updaterExePath, "-I");
// 退出程序
Application.Current.Shutdown();
}
break;
@@ -144,11 +155,118 @@ public class UpdateService : IUpdateService
}
};
win.NavigateToHtml(await GetReleaseMarkdownHtmlAsync());
if (option.Channel == UpdateChannel.Stable)
{
win.NavigateToHtml(await GetReleaseMarkdownHtmlAsync());
}
win.ShowDialog();
}
private async Task<string> GetLatestVersionAsync()
private async Task<string> GetLatestVersionAsync(UpdateOption option)
{
if (option.Channel == UpdateChannel.Stable)
{
return await UpdateFromOss();
}
else
{
return await UpdateFromMirrorChyan();
}
}
/// <summary>
/// 文档
/// https://apifox.com/apidoc/shared/ffdc8453-597d-4ba6-bd3c-5e375c10c789
/// </summary>
/// <returns></returns>
private async Task<string> UpdateFromMirrorChyan()
{
try
{
const string url = "https://mirrorchyan.com/api/resources/BGI/latest";
var queryParams = new Dictionary<string, string>
{
{ "user_agent", "BetterGI" },
{ "os", "win" },
{ "arch", "x64" },
{ "channel", "alpha" }
};
using var httpClient = new HttpClient();
var finalUrl = $"{url}?{string.Join("&", queryParams.Select(x => $"{x.Key}={x.Value}"))}";
var response = await httpClient.GetAsync(finalUrl);
LatestResponse? result = null;
if (response.StatusCode == HttpStatusCode.OK)
{
response.EnsureSuccessStatusCode();
result = await response.Content.ReadFromJsonAsync<LatestResponse>();
}
else
{
// 即使是403、400也尝试读取响应体
var content = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<LatestResponse>(content);
}
if (result != null)
{
if (result.Code == 0)
{
return result.Data.VersionName;
}
else if (result.Code < 0)
{
Toast.Error(
$"Mirror酱源更新检查失败意料之外的严重错误请及时联系 Mirror 酱的技术支持处理\n错误代码{result.Code},错误信息:{result.Msg}");
return string.Empty;
}
else
{
ToastError(result);
}
}
}
catch (Exception e)
{
_logger.LogDebug(e, "Mirror源更新检查失败");
Toast.Warning($"Mirror源更新检查失败,{e.Message}");
}
return string.Empty;
}
private static void ToastError(LatestResponse response)
{
if (response.Code == 7001)
{
Toast.Warning("Mirror酱 CDK 已过期请重新获取CDK");
}
else if (response.Code == 7002)
{
Toast.Warning("Mirror酱 CDK 错误!");
}
else if (response.Code == 7003)
{
Toast.Warning("Mirror酱 CDK 今日下载次数已达上限");
}
else if (response.Code == 7004)
{
Toast.Warning("Mirror酱 CDK 类型和待下载的资源不匹配");
}
else if (response.Code == 7005)
{
Toast.Warning("Mirror酱 CDK 已被封禁");
}
else
{
Toast.Warning($"Mirror酱源更新检查失败错误信息{response.Msg}");
}
}
private async Task<string> UpdateFromOss()
{
try
{
@@ -181,7 +299,9 @@ public class UpdateService : IUpdateService
{
using HttpClient httpClient = new();
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
string jsonString = await httpClient.GetStringAsync("https://api.github.com/repos/babalae/better-genshin-impact/releases/latest");
string jsonString =
await httpClient.GetStringAsync(
"https://api.github.com/repos/babalae/better-genshin-impact/releases/latest");
var jsonDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonString);
if (jsonDict != null)
@@ -191,7 +311,8 @@ public class UpdateService : IUpdateService
string md = $"# {name}{new string('\n', 2)}{body}";
md = WebUtility.HtmlEncode(md);
string md2html = ResourceHelper.GetString($"pack://application:,,,/Assets/Strings/md2html.html", Encoding.UTF8);
string md2html = ResourceHelper.GetString($"pack://application:,,,/Assets/Strings/md2html.html",
Encoding.UTF8);
var html = md2html.Replace("{{content}}", md);
return html;
@@ -240,4 +361,4 @@ public class UpdateService : IUpdateService
</html>
""";
}
}
}

View File

@@ -1176,7 +1176,7 @@
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,20,0"
Command="{Binding OpenKeyBindingsWindowCommand}"
Command="{Binding CheckUpdateCommand}"
Content="检查更新" />
</Grid>
</ui:CardExpander.Header>
@@ -1192,20 +1192,50 @@
</Grid.ColumnDefinitions>
<emoji:TextBlock Grid.Row="0"
Grid.Column="0"
Text="使用 Mirror 酱 更新至测试版"
Text="检查是否存在最新测试版"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="测试版非常不稳定,请谨慎选择更新!"
Text="测试版非常不稳定,请谨慎选择更新!"
TextWrapping="Wrap" />
<StackPanel
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Orientation="Horizontal">
<ui:Button
Margin="0,0,36,0"
Command="{Binding CheckUpdateAlphaCommand}"
Content="检查更新" />
</StackPanel>
</Grid>
<!--<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<emoji:TextBlock Grid.Row="0"
Grid.Column="0"
Text="直接从 Github 获取最新测试版"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="【测试版】非常不稳定,请谨慎选择更新!"
TextWrapping="Wrap" />
<ui:Button Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
Command="{Binding OpenKeyBindingsWindowCommand}"
Content="测试版检查更新" />
</Grid>
Command="{Binding GotoGithubActionCommand}"
Content="访问 Github" />
</Grid>-->
</StackPanel>
</ui:CardExpander>

View File

@@ -11,7 +11,8 @@
x:Name="app"
Title="发现新版本"
Width="680"
Height="800"
MinHeight="10"
SizeToContent="Height"
Background="#202020"
ExtendsContentIntoTitleBar="True"
FontFamily="{DynamicResource TextThemeFontFamily}"
@@ -19,11 +20,13 @@
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid>
<ui:Grid Margin="0,48,0,0" RowDefinitions="Auto,*,Auto,Auto">
<ui:Grid Name="MyGrid" Margin="0,48,0,0" RowDefinitions="Auto,*,Auto,Auto">
<webview:WebpagePanel x:Name="WebpagePanel"
Grid.Row="1"
Height="400"
Margin="12,0,12,0" />
<ui:Grid Grid.Row="0"
<ui:Grid Name="UpdateStatusMessageGrid"
Grid.Row="0"
Margin="16,0,16,0"
ColumnDefinitions="Auto,*"
Visibility="{Binding ShowUpdateStatus, Converter={StaticResource BooleanToVisibilityConverter}}">
@@ -33,8 +36,8 @@
</ui:StackPanel>
</ui:Grid>
<!-- 新增:多渠道更新方式卡片 -->
<StackPanel Grid.Row="2" Margin="12,0,12,0">
<ui:CardControl Margin="0,0,0,8">
<StackPanel Name="ServerPanel" Grid.Row="2" Margin="12,0,12,0">
<ui:CardControl Name="DefaultCard" Margin="0,0,0,8">
<ui:CardControl.Icon>
<ui:FontIcon Glyph="&#xf4ba;" Style="{StaticResource FaFontIconStyle}" />
</ui:CardControl.Icon>
@@ -47,12 +50,12 @@
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="Steambird"
Text="默认更新服务器"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="普通用户可通过 Steambird 渠道直接更新(默认更新渠道)"
Text="普通用户可点此直接更新"
TextWrapping="Wrap" />
</Grid>
</ui:CardControl.Header>
@@ -61,7 +64,7 @@
Appearance="Success"
Icon="{ui:SymbolIcon ArrowDownload24}"
Content="立即更新"
Command="{Binding UpdateCommand}" />
Command="{Binding UpdateFromSteambirdCommand}" />
</StackPanel>
</ui:CardControl>
<ui:CardControl Margin="0,0,0,8">
@@ -86,15 +89,21 @@
</Grid>
</ui:CardControl.Header>
<StackPanel Orientation="Horizontal">
<ui:Button
Appearance="Primary"
<ui:Button
Name="EditCdkButton"
Margin="0,0,8,0"
Icon="{ui:SymbolIcon TicketDiagonal24}"
Content="输入CDK更新"
Command="{Binding OneKeyExecuteCommand}" />
Content="修改CDK"
Command="{Binding EditCdkCommand}" />
<ui:Button
Appearance="Success"
Icon="{ui:SymbolIcon ArrowDownload24}"
Content="立即更新"
Command="{Binding UpdateFromMirrorChyanCommand}" />
</StackPanel>
</ui:CardControl>
<ui:CardControl Margin="0,0,0,8">
<!--<ui:CardControl Margin="0,0,0,8">
<ui:CardControl.Icon>
<ui:FontIcon Glyph="&#xf0c2;" Style="{StaticResource FaFontIconStyle}" />
</ui:CardControl.Icon>
@@ -122,7 +131,7 @@
Content="唤起胡桃"
Command="{Binding OneKeyExecuteCommand}" />
</StackPanel>
</ui:CardControl>
</ui:CardControl>-->
</StackPanel>
<!-- 原有按钮区域 -->
<ui:Grid Grid.Row="3"

View File

@@ -2,10 +2,17 @@
using CommunityToolkit.Mvvm.Input;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using Windows.System;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Helpers.Win32;
using BetterGenshinImpact.Model;
using Meziantou.Framework.Win32;
using Wpf.Ui.Controls;
using Wpf.Ui.Violeta.Controls;
namespace BetterGenshinImpact.View.Windows;
@@ -14,22 +21,59 @@ public partial class CheckUpdateWindow : FluentWindow
{
public Func<object, CheckUpdateWindowButton, Task>? UserInteraction = null!;
[ObservableProperty]
private bool showUpdateStatus = false;
[ObservableProperty] private bool showUpdateStatus = false;
[ObservableProperty]
private string updateStatusMessage = string.Empty;
[ObservableProperty] private string updateStatusMessage = string.Empty;
private UpdateOption _option;
public CheckUpdateWindow(UpdateOption option)
{
_option = option ?? throw new ArgumentNullException(nameof(option));
DataContext = this;
InitializeComponent();
// 存在CDK则显示修改按钮
if (string.IsNullOrEmpty(MirrorChyanHelper.GetCdk()))
{
EditCdkButton.Visibility = Visibility.Collapsed;
}
if (option.Trigger == UpdateTrigger.Manual)
{
IgnoreButton.Visibility = Visibility.Collapsed;
}
if (option.Channel == UpdateChannel.Alpha)
{
WebpagePanel.Height = 0;
WebpagePanel.Visibility = Visibility.Collapsed;
UpdateStatusMessageGrid.Height = 0;
ShowUpdateStatus = false;
// 删除前两行
MyGrid.RowDefinitions.RemoveAt(0);
MyGrid.RowDefinitions.RemoveAt(0);
// 注意:删除行定义后,需要调整剩余元素的 Grid.Row 属性
foreach (FrameworkElement child in MyGrid.Children)
{
int currentRow = System.Windows.Controls.Grid.GetRow(child);
if (currentRow > 1) // 如果元素在第三行或之后
{
Grid.SetRow(child, currentRow - 2); // 行号减2
}
}
if (ServerPanel.Children.Count > 0)
{
ServerPanel.Children.RemoveAt(0);
}
SizeToContent = SizeToContent.Height; // 设置高度为自动
UpdateLayout();
}
Closing += OnClosing;
}
@@ -73,6 +117,55 @@ public partial class CheckUpdateWindow : FluentWindow
}
}
[RelayCommand]
private async Task UpdateFromSteambirdAsync()
{
await RunUpdaterAsync("-I");
}
[RelayCommand]
private async Task UpdateFromMirrorChyanAsync()
{
var cdk = MirrorChyanHelper.GetAndPromptCdk();
if (string.IsNullOrEmpty(cdk))
{
return;
}
if (_option.Channel == UpdateChannel.Stable)
{
await RunUpdaterAsync("--source mirrorc");
}
else
{
await RunUpdaterAsync("--source mirrorc-alpha");
}
}
/// <summary>
/// --source mirrorc
/// --source mirrorc-alpha
/// --source github
/// --dfs-extras {"hutao-token": "...."}
/// </summary>
private async Task RunUpdaterAsync(string parameters)
{
// 唤起更新程序
string updaterExePath = Global.Absolute("BetterGI.update.exe");
if (!File.Exists(updaterExePath))
{
await MessageBox.ErrorAsync("更新程序不存在,请选择其他更新方式!");
return;
}
// 启动
Process.Start(updaterExePath, parameters);
// 退出程序
Application.Current.Shutdown();
}
[RelayCommand]
private async Task IgnoreAsync()
{
@@ -90,6 +183,12 @@ public partial class CheckUpdateWindow : FluentWindow
await UserInteraction.Invoke(this, CheckUpdateWindowButton.Cancel);
}
}
[RelayCommand]
private void EditCdk()
{
MirrorChyanHelper.EditCdk();
}
public enum CheckUpdateWindowButton
{

View File

@@ -28,21 +28,36 @@
<ui:TextBlock Name="TxtQuestion" Margin="5" />
<!-- <ui:TextBox Name="TxtResponse" Margin="5" /> -->
<ContentControl Name="DynamicContent" Margin="5" />
<StackPanel Margin="5"
HorizontalAlignment="Right"
Orientation="Horizontal">
<ui:Button Name="BtnOk"
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- 左下角按钮 -->
<ui:Button Name="BtnLeftBottom"
Margin="5"
Appearance="Primary"
Click="BtnOkClick"
Content="确定"
IsDefault="True" />
<ui:Button Name="BtnCancel"
Margin="5"
Click="BtnCancelClick"
Content="取消"
IsCancel="True" />
</StackPanel>
HorizontalAlignment="Left"
Appearance="Success"
Content="左下角按钮"
Grid.Column="0" />
<!-- 原有的右侧按钮 -->
<StackPanel Grid.Column="1"
Orientation="Horizontal">
<ui:Button Name="BtnOk"
Margin="5"
Appearance="Primary"
Click="BtnOkClick"
Content="确定"
IsDefault="True" />
<ui:Button Name="BtnCancel"
Margin="5"
Click="BtnCancelClick"
Content="取消"
IsCancel="True" />
</StackPanel>
</Grid>
</StackPanel>
<ui:TitleBar Name="MyTitleBar" Grid.Row="0">

View File

@@ -3,13 +3,37 @@ using System.Windows.Controls;
namespace BetterGenshinImpact.View.Windows;
/// <summary>
/// 对话框配置类,用于控制对话框中的元素显示
/// </summary>
public class PromptDialogConfig
{
/// <summary>
/// 是否显示左下角按钮
/// </summary>
public bool ShowLeftButton { get; set; } = false;
/// <summary>
/// 左下角按钮的文本
/// </summary>
public string LeftButtonText { get; set; } = "左下角按钮";
/// <summary>
/// 左下角按钮的点击事件
/// </summary>
public RoutedEventHandler? LeftButtonClick { get; set; }
}
public partial class PromptDialog
{
public PromptDialog(string question, string title, UIElement uiElement, string defaultValue)
private readonly PromptDialogConfig _config;
public PromptDialog(string question, string title, UIElement uiElement, string defaultValue, PromptDialogConfig? config = null)
{
InitializeComponent();
MyTitleBar.Title = title;
TxtQuestion.Text = question;
_config = config ?? new PromptDialogConfig();
DynamicContent.Content = uiElement;
if (DynamicContent.Content is TextBox textBox)
@@ -21,31 +45,50 @@ public partial class PromptDialog
comboBox.Text = defaultValue;
}
// 配置左下角按钮
ConfigureLeftButton();
this.Loaded += PromptDialogLoaded;
}
private void ConfigureLeftButton()
{
if (_config.ShowLeftButton)
{
BtnLeftBottom.Content = _config.LeftButtonText;
if (_config.LeftButtonClick != null)
{
BtnLeftBottom.Click += _config.LeftButtonClick;
}
}
else
{
BtnLeftBottom.Visibility = Visibility.Collapsed;
}
}
private void PromptDialogLoaded(object sender, RoutedEventArgs e)
{
DynamicContent.Focus();
}
public static string Prompt(string question, string title, string defaultValue = "")
public static string Prompt(string question, string title, string defaultValue = "", PromptDialogConfig? config = null)
{
var inst = new PromptDialog(question, title, new TextBox(), defaultValue);
var inst = new PromptDialog(question, title, new TextBox(), defaultValue, config);
inst.ShowDialog();
return inst.DialogResult == true ? inst.ResponseText : defaultValue;
}
public static string Prompt(string question, string title, UIElement uiElement, string defaultValue = "")
public static string Prompt(string question, string title, UIElement uiElement, string defaultValue = "", PromptDialogConfig? config = null)
{
var inst = new PromptDialog(question, title, uiElement, defaultValue);
var inst = new PromptDialog(question, title, uiElement, defaultValue, config);
inst.ShowDialog();
return inst.DialogResult == true ? inst.ResponseText : defaultValue;
}
public static string Prompt(string question, string title, UIElement uiElement, Size size)
public static string Prompt(string question, string title, UIElement uiElement, Size size, PromptDialogConfig? config = null)
{
var inst = new PromptDialog(question, title, uiElement, "")
var inst = new PromptDialog(question, title, uiElement, "", config)
{
Width = size.Width,
Height = size.Height

View File

@@ -9,6 +9,7 @@ using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Windows.System;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Script;
@@ -16,6 +17,8 @@ using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Helpers.Win32;
using BetterGenshinImpact.Model;
using BetterGenshinImpact.Service.Interface;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.View.Converters;
@@ -25,9 +28,11 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Meziantou.Framework.Win32;
using Microsoft.Extensions.Localization;
using Microsoft.Win32;
using Wpf.Ui;
using Wpf.Ui.Violeta.Controls;
namespace BetterGenshinImpact.ViewModel.Pages;
@@ -42,8 +47,8 @@ public partial class CommonSettingsPageViewModel : ViewModel
private string _selectedCountry = string.Empty;
[ObservableProperty]
private List<string> _adventurersGuildCountry = ["无","枫丹", "稻妻", "璃月", "蒙德"];
[ObservableProperty] private List<string> _adventurersGuildCountry = ["无", "枫丹", "稻妻", "璃月", "蒙德"];
public CommonSettingsPageViewModel(IConfigService configService, INavigationService navigationService,
NotificationService notificationService)
{
@@ -57,17 +62,18 @@ public partial class CommonSettingsPageViewModel : ViewModel
public ObservableCollection<string> CountryList { get; } = new();
public ObservableCollection<string> Areas { get; } = new();
[ObservableProperty]
private FrozenDictionary<string, string> _languageDict = new string[] { "zh-Hans", "zh-Hant", "en", "fr" }
.ToFrozenDictionary(
c => c,
c =>
{
CultureInfo.CurrentUICulture = new CultureInfo(c);
var stringLocalizer = App.GetService<IStringLocalizer<CultureInfoNameToKVPConverter>>() ?? throw new NullReferenceException();
return stringLocalizer["简体中文"].ToString();
}
);
[ObservableProperty] private FrozenDictionary<string, string> _languageDict =
new string[] { "zh-Hans", "zh-Hant", "en", "fr" }
.ToFrozenDictionary(
c => c,
c =>
{
CultureInfo.CurrentUICulture = new CultureInfo(c);
var stringLocalizer = App.GetService<IStringLocalizer<CultureInfoNameToKVPConverter>>() ??
throw new NullReferenceException();
return stringLocalizer["简体中文"].ToString();
}
);
public string SelectedCountry
{
@@ -238,7 +244,7 @@ public partial class CommonSettingsPageViewModel : ViewModel
}
}
}
[RelayCommand]
private void OpenAboutWindow()
{
@@ -246,7 +252,7 @@ public partial class CommonSettingsPageViewModel : ViewModel
aboutWindow.Owner = Application.Current.MainWindow;
aboutWindow.ShowDialog();
}
[RelayCommand]
private void OpenKeyBindingsWindow()
{
@@ -260,4 +266,31 @@ public partial class CommonSettingsPageViewModel : ViewModel
{
await OcrFactory.ChangeCulture(type.Key);
}
[RelayCommand]
private async Task CheckUpdateAsync()
{
await App.GetService<IUpdateService>()!.CheckUpdateAsync(new UpdateOption
{
Trigger = UpdateTrigger.Manual,
Channel = UpdateChannel.Stable
});
}
[RelayCommand]
private async Task CheckUpdateAlphaAsync()
{
await App.GetService<IUpdateService>()!.CheckUpdateAsync(new UpdateOption
{
Trigger = UpdateTrigger.Manual,
Channel = UpdateChannel.Alpha,
});
}
[RelayCommand]
private async Task GotoGithubActionAsync()
{
await Launcher.LaunchUriAsync(
new Uri("https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml"));
}
}