diff --git a/src/MaaWpfGui/Constants/ConfigurationKeys.cs b/src/MaaWpfGui/Constants/ConfigurationKeys.cs index e93626a4be..9e9eb785aa 100644 --- a/src/MaaWpfGui/Constants/ConfigurationKeys.cs +++ b/src/MaaWpfGui/Constants/ConfigurationKeys.cs @@ -187,5 +187,8 @@ namespace MaaWpfGui.Constants public const string ForceScheduledStart = "Timer.ForceScheduledStart"; public const string CustomConfig = "Timer.CustomConfig"; + + public const string AnnouncementInfo = "Announcement.AnnouncementInfo"; + public const string IsFirstShowAnnouncement = "Announcement.IsFirstShowAnnouncement"; } } diff --git a/src/MaaWpfGui/Helper/Instances.cs b/src/MaaWpfGui/Helper/Instances.cs index b5eb7f3cbe..cbd4566c4d 100644 --- a/src/MaaWpfGui/Helper/Instances.cs +++ b/src/MaaWpfGui/Helper/Instances.cs @@ -62,6 +62,8 @@ namespace MaaWpfGui.Helper public static VersionUpdateViewModel VersionUpdateViewModel { get; private set; } + public static AnnouncementViewModel AnnouncementViewModel { get; private set; } + public static AsstProxy AsstProxy { get; private set; } public static TrayIcon TrayIcon { get; private set; } @@ -88,6 +90,7 @@ namespace MaaWpfGui.Helper SettingsViewModel = container.Get(); CopilotViewModel = container.Get(); VersionUpdateViewModel = container.Get(); + AnnouncementViewModel = container.Get(); // 这仨实例化时存在依赖顺序 HttpService = container.Get(); diff --git a/src/MaaWpfGui/Main/Bootstrapper.cs b/src/MaaWpfGui/Main/Bootstrapper.cs index ad612c360b..803ae1fbef 100644 --- a/src/MaaWpfGui/Main/Bootstrapper.cs +++ b/src/MaaWpfGui/Main/Bootstrapper.cs @@ -16,6 +16,7 @@ using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Security.Principal; +using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; using GlobalHotKey; @@ -193,6 +194,17 @@ namespace MaaWpfGui.Main /// protected override void OnLaunch() { + Task.Run(async () => + { + if (!Instances.AnnouncementViewModel.IsFirstShowAnnouncement) + { + return; + } + + await Instances.AnnouncementViewModel.CheckAndDownloadAnnouncement(); + _ = Execute.OnUIThreadAsync(() => Instances.WindowManager.ShowWindow(Instances.AnnouncementViewModel)); + Instances.AnnouncementViewModel.IsFirstShowAnnouncement = false; + }); Instances.VersionUpdateViewModel.ShowUpdateOrDownload(); } diff --git a/src/MaaWpfGui/Res/Localizations/en-us.xaml b/src/MaaWpfGui/Res/Localizations/en-us.xaml index 800f70763a..72afe20089 100644 --- a/src/MaaWpfGui/Res/Localizations/en-us.xaml +++ b/src/MaaWpfGui/Res/Localizations/en-us.xaml @@ -670,4 +670,8 @@ The video aspect ratio needs to be 16:9 without interference factors such as bla MAA Copilot (Chinese) Doctor, there's a new notification! + + Announcement + View Announcement + diff --git a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml index 40f62a1835..a20c48c758 100644 --- a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml +++ b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml @@ -670,4 +670,8 @@ Bilibili: ログイン インターフェイスに表示されるアカウント 公式サイト Copilot(中国語) + + 公告 + 公告を見ます + diff --git a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml index bfbe874d47..e35afd7fe7 100644 --- a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml +++ b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml @@ -670,4 +670,8 @@ Bilibili: 로그인 인터페이스에 표시되는 계정 이름(예: 장산) 공식 홈페이지 워크 스테이션 + + 공고패 + 공고패 + diff --git a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml index 9570ae05a4..eb4565f272 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml @@ -670,4 +670,8 @@ 官网 作业站 + + 公告 + 查看公告 + diff --git a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml index 427bde3f97..6136a18647 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml @@ -670,4 +670,8 @@ 官網 作業站 + + 公告 + 查看公告 + diff --git a/src/MaaWpfGui/ViewModels/UI/AnnouncementViewModel.cs b/src/MaaWpfGui/ViewModels/UI/AnnouncementViewModel.cs new file mode 100644 index 0000000000..904610463b --- /dev/null +++ b/src/MaaWpfGui/ViewModels/UI/AnnouncementViewModel.cs @@ -0,0 +1,125 @@ +// +// MaaWpfGui - A part of the MaaCoreArknights project +// Copyright (C) 2021 MistEO and Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Threading.Tasks; +using System.Windows.Documents; +using System.Windows.Input; +using MaaWpfGui.Constants; +using MaaWpfGui.Helper; +using Markdig; +using Markdig.Wpf; +using Stylet; + +namespace MaaWpfGui.ViewModels.UI +{ + /// + /// The view model of version update. + /// + // 通过 container.Get(); 实例化或获取实例 + // ReSharper disable once ClassNeverInstantiated.Global + public class AnnouncementViewModel : Screen + { + private string _announcementInfo = ConfigurationHelper.GetValue(ConfigurationKeys.AnnouncementInfo, string.Empty); + + /// + /// Gets the announcement info. + /// + // ReSharper disable once MemberCanBePrivate.Global + public string AnnouncementInfo + { + get => _announcementInfo; + private set + { + SetAndNotify(ref _announcementInfo, value); + ConfigurationHelper.SetValue(ConfigurationKeys.AnnouncementInfo, value); + } + } + + private bool _isFirstShowAnnouncement = bool.Parse(ConfigurationHelper.GetValue(ConfigurationKeys.IsFirstShowAnnouncement, true.ToString())); + + public bool IsFirstShowAnnouncement + { + get => _isFirstShowAnnouncement; + set + { + SetAndNotify(ref _isFirstShowAnnouncement, value); + ConfigurationHelper.SetValue(ConfigurationKeys.IsFirstShowAnnouncement, value.ToString()); + } + } + + public FlowDocument AnnouncementInfoDoc => Markdig.Wpf.Markdown.ToFlowDocument(AnnouncementInfo, + new MarkdownPipelineBuilder().UseSupportedExtensions().Build()); + + /// + /// 检查更新 + /// + public async Task CheckAndDownloadAnnouncement() + { + const string Path = "announcements/wpf.md"; + const string Url = MaaUrls.MaaApi + Path; + + var etag = !string.IsNullOrEmpty(AnnouncementInfo) ? ETagCache.Get(Url) : string.Empty; + Dictionary header = new Dictionary + { + { "Accept", "application/octet-stream" }, + }; + + if (!string.IsNullOrEmpty(etag)) + { + header["If-None-Match"] = etag; + } + + var response = await Instances.HttpService.GetAsync(new Uri(Url), header, httpCompletionOption: HttpCompletionOption.ResponseHeadersRead); + if (response == null || + response.StatusCode == System.Net.HttpStatusCode.NotModified || + response.StatusCode != System.Net.HttpStatusCode.OK) + { + return; + } + + try + { + var body = await response.Content.ReadAsStringAsync(); + if (!string.IsNullOrEmpty(body)) + { + AnnouncementInfo = body; + IsFirstShowAnnouncement = true; + } + } + catch + { + return; + } + + ETagCache.Set(Url, response.Headers.ETag.Tag); + ETagCache.Save(); + } + + /// + /// The event handler of opening hyperlink. + /// + /// The sender. + /// The event arguments. + // xaml 里用到了 + // ReSharper disable once UnusedMember.Global + // ReSharper disable once UnusedParameter.Global + public void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) + { + Process.Start(e.Parameter.ToString()); + } + } +} diff --git a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs index de321f090a..b8121d386f 100644 --- a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs @@ -3727,5 +3727,13 @@ namespace MaaWpfGui.ViewModels.UI { (sender as ComboBox)?.MakeComboBoxSearchable(); } + + // UI 绑定的方法 + // ReSharper disable once UnusedMember.Global + public async Task CheckAndDownloadAnnouncement() + { + await Instances.AnnouncementViewModel.CheckAndDownloadAnnouncement(); + _ = Execute.OnUIThreadAsync(() => Instances.WindowManager.ShowWindow(Instances.AnnouncementViewModel)); + } } } diff --git a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs index 98db4dd43b..57875f6139 100644 --- a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs @@ -395,24 +395,6 @@ namespace MaaWpfGui.ViewModels.UI { IsFirstBootAfterUpdate = false; Instances.WindowManager.ShowWindow(this); - //if (false) // _curVersion == "v4.24.0" - //{ - // var result = MessageBoxHelper.Show( - // LocalizationHelper.GetString("Dotnet8"), - // LocalizationHelper.GetString("Dotnet8Caption"), - // MessageBoxButton.YesNo, - // MessageBoxImage.Information); - // switch (result) - // { - // case MessageBoxResult.No: - // Instances.SettingsViewModel.AutoDownloadUpdatePackage = false; - // break; - - // case MessageBoxResult.Yes: - // Process.Start("https://dotnet.microsoft.com/download/dotnet/8.0/runtime"); - // break; - // } - //} } else { @@ -992,6 +974,7 @@ namespace MaaWpfGui.ViewModels.UI /// The event arguments. // xaml 里用到了 // ReSharper disable once UnusedMember.Global + // ReSharper disable once UnusedParameter.Global public void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) { Process.Start(e.Parameter.ToString()); diff --git a/src/MaaWpfGui/Views/UI/AnnouncementView.xaml b/src/MaaWpfGui/Views/UI/AnnouncementView.xaml new file mode 100644 index 0000000000..0994a7435e --- /dev/null +++ b/src/MaaWpfGui/Views/UI/AnnouncementView.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MaaWpfGui/Views/UserControl/AboutUserControl.xaml b/src/MaaWpfGui/Views/UserControl/AboutUserControl.xaml index 4b759e5f14..d0c8af1a4a 100644 --- a/src/MaaWpfGui/Views/UserControl/AboutUserControl.xaml +++ b/src/MaaWpfGui/Views/UserControl/AboutUserControl.xaml @@ -17,101 +17,115 @@ d:DataContext="{d:DesignInstance {x:Type ui:SettingsViewModel}}" d:DesignWidth="550" mc:Ignorable="d"> - - - - - - + + + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - +