From e6d1867b262218a727a17d0bd3331fe1de4d2f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 12 Oct 2025 19:30:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=85=91=E6=8D=A2=E7=A0=81?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E4=BB=A5=E6=98=BE=E7=A4=BA=E6=9C=80=E6=96=B0?= =?UTF-8?q?=E5=85=91=E6=8D=A2=E7=A0=81=E5=8A=A8=E6=80=81=20(#2276)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Config/CommonConfig.cs | 7 + .../GamePreviewLiveDateCalculator.cs | 51 ++++ .../UseRedeemCode/RedeemCodeManager.cs | 6 + .../BooleanToVisibilityRevertConverter.cs | 10 +- .../View/Converters/NotNullConverter.cs | 5 + BetterGenshinImpact/View/MainWindow.xaml | 17 +- .../View/Windows/FeedWindow.xaml | 266 ++++++++++++++++++ .../View/Windows/FeedWindow.xaml.cs | 41 +++ .../ViewModel/MainWindowViewModel.cs | 57 +++- .../ViewModel/Windows/FeedWindowViewModel.cs | 168 +++++++++++ 10 files changed, 622 insertions(+), 6 deletions(-) create mode 100644 BetterGenshinImpact/GameTask/UseRedeemCode/GamePreviewLiveDateCalculator.cs create mode 100644 BetterGenshinImpact/View/Windows/FeedWindow.xaml create mode 100644 BetterGenshinImpact/View/Windows/FeedWindow.xaml.cs create mode 100644 BetterGenshinImpact/ViewModel/Windows/FeedWindowViewModel.cs diff --git a/BetterGenshinImpact/Core/Config/CommonConfig.cs b/BetterGenshinImpact/Core/Config/CommonConfig.cs index 7510aa1f..338c3d64 100644 --- a/BetterGenshinImpact/Core/Config/CommonConfig.cs +++ b/BetterGenshinImpact/Core/Config/CommonConfig.cs @@ -73,4 +73,11 @@ public partial class CommonConfig : ObservableObject /// [ObservableProperty] private List _onceHadRunDeviceIdList = new(); + + + /// + /// 当前看过的兑换码推送版本 + /// + [ObservableProperty] + private string _redeemCodeFeedsUpdateVersion = "20251013"; } diff --git a/BetterGenshinImpact/GameTask/UseRedeemCode/GamePreviewLiveDateCalculator.cs b/BetterGenshinImpact/GameTask/UseRedeemCode/GamePreviewLiveDateCalculator.cs new file mode 100644 index 00000000..c56c8fa5 --- /dev/null +++ b/BetterGenshinImpact/GameTask/UseRedeemCode/GamePreviewLiveDateCalculator.cs @@ -0,0 +1,51 @@ +using System; + +namespace BetterGenshinImpact.GameTask.UseRedeemCode; + +public class GamePreviewLiveDateCalculator +{ + private static readonly DateTime StartDate = new DateTime(2025, 10, 10); + private const int IntervalDays = 42; + private const double ValidDays = 3.5; + + /// + /// 计算当前日期是否是前瞻日期 + /// + /// 如果是前瞻日期,返回 true;否则返回 false。 + public static bool IsPreviewDate(DateTime date) + { + TimeSpan difference = date - StartDate; + return difference.Days >= 0 && difference.Days % IntervalDays == 0; + } + + public static void TestIsPreviewDate() + { + IsPreviewDate(new DateTime(2025, 11, 21)); + } + + public bool TestTodayIsPreviewDate() + { + return IsPreviewDate(DateTime.Today); + } + + /// + /// 计算当前时间是否在从前瞻日期开始的2.5天范围内。 + /// + /// 如果在范围内,返回 true;否则返回 false。 + public static bool IsWithinPreviewRange(DateTime now) + { + TimeSpan difference = now.Date - StartDate; + int daysSinceStart = difference.Days; + + if (daysSinceStart < 0) + { + return false; + } + + int intervalCount = daysSinceStart / IntervalDays; + DateTime lastPreviewDate = StartDate.AddDays(intervalCount * IntervalDays); + TimeSpan timeSinceLastPreview = now - lastPreviewDate; + + return timeSinceLastPreview.TotalDays >= 0 && timeSinceLastPreview.TotalDays <= ValidDays; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/UseRedeemCode/RedeemCodeManager.cs b/BetterGenshinImpact/GameTask/UseRedeemCode/RedeemCodeManager.cs index 131f6cf5..31f77b66 100644 --- a/BetterGenshinImpact/GameTask/UseRedeemCode/RedeemCodeManager.cs +++ b/BetterGenshinImpact/GameTask/UseRedeemCode/RedeemCodeManager.cs @@ -13,6 +13,12 @@ namespace BetterGenshinImpact.GameTask.UseRedeemCode; public class RedeemCodeManager { public static HashSet CancelClipboardHash { get; } = []; + + public static void AddNotDetectClipboardText(string clipboardText) + { + var md5Hash = MD5Helper.ComputeMD5(clipboardText); + CancelClipboardHash.Add(md5Hash); + } public static async Task ImportFromClipboard(string clipboardText) { diff --git a/BetterGenshinImpact/View/Converters/BooleanToVisibilityRevertConverter.cs b/BetterGenshinImpact/View/Converters/BooleanToVisibilityRevertConverter.cs index 4b90e5bb..f6dd2d45 100644 --- a/BetterGenshinImpact/View/Converters/BooleanToVisibilityRevertConverter.cs +++ b/BetterGenshinImpact/View/Converters/BooleanToVisibilityRevertConverter.cs @@ -9,7 +9,15 @@ public sealed class BooleanToVisibilityRevertConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - return (value is bool v && v) ? Visibility.Collapsed : Visibility.Visible; + if (value is string str) + { + return string.IsNullOrEmpty(str) ? Visibility.Collapsed : Visibility.Visible; + } + else if (value is bool b) + { + return b ? Visibility.Collapsed : Visibility.Visible; + } + return Visibility.Visible; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) diff --git a/BetterGenshinImpact/View/Converters/NotNullConverter.cs b/BetterGenshinImpact/View/Converters/NotNullConverter.cs index 1e745d8d..945d721b 100644 --- a/BetterGenshinImpact/View/Converters/NotNullConverter.cs +++ b/BetterGenshinImpact/View/Converters/NotNullConverter.cs @@ -8,6 +8,11 @@ namespace BetterGenshinImpact.View.Converters { public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { + if (value is string str) + { + return !string.IsNullOrWhiteSpace(str); + } + return value != null; } diff --git a/BetterGenshinImpact/View/MainWindow.xaml b/BetterGenshinImpact/View/MainWindow.xaml index df539d00..67a130d0 100644 --- a/BetterGenshinImpact/View/MainWindow.xaml +++ b/BetterGenshinImpact/View/MainWindow.xaml @@ -178,13 +178,22 @@ - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BetterGenshinImpact/View/Windows/FeedWindow.xaml.cs b/BetterGenshinImpact/View/Windows/FeedWindow.xaml.cs new file mode 100644 index 00000000..cf001821 --- /dev/null +++ b/BetterGenshinImpact/View/Windows/FeedWindow.xaml.cs @@ -0,0 +1,41 @@ +using BetterGenshinImpact.Helpers.Ui; +using BetterGenshinImpact.ViewModel.Windows; +using System; +using System.Windows; + +namespace BetterGenshinImpact.View.Windows; + +public partial class FeedWindow +{ + public FeedWindowViewModel ViewModel { get; } + + public FeedWindow(FeedWindowViewModel viewModel) + { + DataContext = ViewModel = viewModel; + InitializeComponent(); + + this.Loaded += FeedWindow_Loaded; + this.SourceInitialized += FeedWindow_SourceInitialized; + } + + public FeedWindow() : this(new FeedWindowViewModel()) + { + } + + private void FeedWindow_SourceInitialized(object? sender, EventArgs e) + { + // 应用与主窗口相同的背景主题 + WindowHelper.TryApplySystemBackdrop(this); + } + + private void FeedWindow_Loaded(object sender, RoutedEventArgs e) + { + // 窗口加载完成后拉取远程兑换码数据 + _ = ViewModel.LoadRemoteDataAsync(); + } + + private void BtnCloseClick(object sender, RoutedEventArgs e) + { + Close(); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs index d137cbf2..e1bba9ca 100644 --- a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Recognition.OCR; using BetterGenshinImpact.Core.Script; using BetterGenshinImpact.GameTask; @@ -8,6 +8,7 @@ using BetterGenshinImpact.Helpers.Ui; using BetterGenshinImpact.Model; using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.View; +using BetterGenshinImpact.View.Pages; using BetterGenshinImpact.View.Windows; using BetterGenshinImpact.ViewModel.Pages; using CommunityToolkit.Mvvm.ComponentModel; @@ -28,6 +29,8 @@ using System.Net.Http.Json; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; +using BetterGenshinImpact.Helpers.Http; +using BetterGenshinImpact.ViewModel.Windows; using Wpf.Ui; using Wpf.Ui.Controls; @@ -37,6 +40,7 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel { private readonly ILogger _logger; private readonly IConfigService _configService; + private readonly INavigationService _navigationService; public string Title => $"BetterGI · 更好的原神 · {Global.Version}{(RuntimeHelper.IsDebug ? " · Dev" : string.Empty)}"; [ObservableProperty] private bool _isVisible = true; @@ -46,6 +50,10 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel [ObservableProperty] private WindowBackdropType _currentBackdropType = WindowBackdropType.Auto; [ObservableProperty] private bool _isWin11Later = OsVersionHelper.IsWindows11_OrGreater; + + [ObservableProperty] private Brush _redeemCodeButtonForeground = Brushes.White; + + private string? _redeemCodeUpdateNewVersion; private bool _firstActivated = true; @@ -53,6 +61,7 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel public MainWindowViewModel(INavigationService navigationService, IConfigService configService) { + _navigationService = navigationService; _configService = configService; Config = _configService.Get(); _logger = App.GetLogger(); @@ -200,6 +209,19 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel } } + [RelayCommand] + private void OnOpenFeed() + { + if (_redeemCodeUpdateNewVersion != null) + { + Config.CommonConfig.RedeemCodeFeedsUpdateVersion = _redeemCodeUpdateNewVersion; + RedeemCodeButtonForeground = Brushes.White; + } + + var feedWindow = new FeedWindow(new FeedWindowViewModel()); + feedWindow.Show(); + } + [RelayCommand] private async Task OnLoaded() { @@ -242,6 +264,9 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel // 检查更新 await App.GetService()!.CheckUpdateAsync(new UpdateOption()); + + // 检查兑换码更新 + await CheckRedeemCodeFeedsUpdateAsync(); // Win11下 BitBlt截图方式不可用,需要关闭窗口优化功能 if (OsVersionHelper.IsWindows11_OrGreater && TaskContext.Instance().Config.AutoFixWin11BitBlt) @@ -410,4 +435,34 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel _configService.Save(); } } + + private async Task CheckRedeemCodeFeedsUpdateAsync() + { + try + { + var request = new HttpRequestMessage(HttpMethod.Get, "https://cnb.cool/bettergi/genshin-redeem-code/-/git/raw/main/update_time.txt"); + var response = await HttpClientFactory.GetCommonSendClient().SendAsync(request); + response.EnsureSuccessStatusCode(); + var txt = await response.Content.ReadAsStringAsync(); + + + if (!string.IsNullOrEmpty(txt)) + { + if (long.TryParse(txt, out long v2) + && long.TryParse(Config.CommonConfig.RedeemCodeFeedsUpdateVersion, out long v1)) + { + if (v2 > v1) + { + RedeemCodeButtonForeground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1E9BFA")); + _redeemCodeUpdateNewVersion = txt; + } + } + } + + } + catch (Exception ex) + { + _logger.LogDebug(ex, $"获取兑换码是否存在更新失败"); + } + } } \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/Windows/FeedWindowViewModel.cs b/BetterGenshinImpact/ViewModel/Windows/FeedWindowViewModel.cs new file mode 100644 index 00000000..01764c43 --- /dev/null +++ b/BetterGenshinImpact/ViewModel/Windows/FeedWindowViewModel.cs @@ -0,0 +1,168 @@ +using BetterGenshinImpact.ViewModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using System.Windows; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.GameTask.UseRedeemCode; +using BetterGenshinImpact.Helpers; +using BetterGenshinImpact.Helpers.Http; +using Newtonsoft.Json; +using Wpf.Ui.Violeta.Controls; + +namespace BetterGenshinImpact.ViewModel.Windows; + +public partial class FeedWindowViewModel : ViewModel +{ + [ObservableProperty] private ObservableCollection _feedItems = new(); + [ObservableProperty] private bool _isLoading; + [ObservableProperty] private bool _isDisplayBtnGetLiveCodes; + + private readonly HttpClient _httpClient = HttpClientFactory.GetCommonSendClient(); + private const string CodesJsonUrl = "https://cnb.cool/bettergi/genshin-redeem-code/-/git/raw/main/codes.json"; + + public FeedWindowViewModel() + { + IsDisplayBtnGetLiveCodes = GamePreviewLiveDateCalculator.IsWithinPreviewRange(DateTime.Now); + } + + [RelayCommand] + private async Task GetLiveRedeemCodes() + { + IsLoading = true; + try + { + var getter = new GetLiveRedeemCode(); + var codeList = await getter.GetCodeMsgAsync(); + + if (codeList.Count == 0) + { + Toast.Warning("暂无前瞻兑换码信息"); + return; + } + + var displayItems = codeList + .Select(c => string.IsNullOrWhiteSpace(c.Items) ? null : c.Items) + .Where(s => !string.IsNullOrWhiteSpace(s)) + .ToList(); + + var item = new FeedItem + { + Title = "【实时获取】前瞻直播兑换码", + Content = displayItems.Count > 0 ? string.Join("\n", displayItems) : string.Empty, + Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm"), + Codes = codeList.Select(c => c.Code).ToList() + }; + + // 插入到列表顶部,方便查看 + if (FeedItems.Count > 0 && FeedItems[0].Title == item.Title) + { + // 如果已经存在相同标题的项,则更新内容和时间 + FeedItems[0].Content = item.Content; + FeedItems[0].Time = item.Time; + FeedItems[0].Codes = item.Codes; + } + else + { + FeedItems.Insert(0, item); + } + + + Toast.Success("已实时获取前瞻兑换码"); + } + catch (Exception ex) + { + Toast.Error($"获取前瞻兑换码失败: {ex.Message}"); + } + finally + { + IsLoading = false; + } + } + + [RelayCommand] + private async Task Refresh() + { + await LoadRemoteDataAsync(); + } + + [RelayCommand] + private void CopyItemCodes(FeedItem item) + { + try + { + if (item?.Codes != null && item.Codes.Any()) + { + var codes = string.Join("\n", item.Codes); + UIDispatcherHelper.Invoke(() => Clipboard.SetDataObject(codes)); + RedeemCodeManager.AddNotDetectClipboardText(codes); + Toast.Information("兑换码已复制到剪贴板"); + } + } + catch (Exception ex) + { + Toast.Error($"复制兑换码失败: {ex.Message}"); + } + } + + [RelayCommand] + private async Task AutoRedeemItem(FeedItem item) + { + if (item?.Codes != null && item.Codes.Count != 0) + { + await new TaskRunner().RunSoloTaskAsync(new UseRedemptionCodeTask(item.Codes)); + } + } + + public async Task LoadRemoteDataAsync() + { + IsLoading = true; + try + { + var request = new HttpRequestMessage(HttpMethod.Get, CodesJsonUrl); + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); + + var items = JsonConvert.DeserializeObject>(json) ?? []; + + FeedItems.Clear(); + foreach (var feed in items) + { + // 若存在标签文本,设置 HasTag + feed.HasTag = !string.IsNullOrWhiteSpace(feed.Tag); + FeedItems.Add(feed); + } + } + catch (Exception ex) + { + Toast.Error($"获取兑换码失败: {ex.Message}"); + } + finally + { + IsLoading = false; + } + } +} + +public partial class FeedItem : ObservableObject +{ + [ObservableProperty] private string _title = string.Empty; + + [ObservableProperty] private string _content = string.Empty; + + [ObservableProperty] private string _time = string.Empty; + + [ObservableProperty] private string _tag = string.Empty; + + [ObservableProperty] private bool _hasTag = false; + + [ObservableProperty] private List _codes = new(); + + [ObservableProperty] private string _valid = string.Empty; +} \ No newline at end of file