Files
MaaAssistantArknights/src/MaaWpfGui/ViewModels/UI/AnnouncementViewModel.cs
MistEO 36da60ed9d fix: 统一 WpfGui 工作目录 (#14072)
* fix: 统一WpfGui工作目录

* chore: 漏了的cache目录

* chore: Auto update by pre-commit hooks [skip changelog]

* fix: 多了一层相对目录

* feat: wpf 创建软连接

* feat: 添加 PathsHelper,提取通用地址

* feat: 提取通用目录

* fix: Combine

* fix: 重构依赖后 MaaWpfGui 的 resource 路径丢失 (#14077)

fix: mklink

* chore: 删除弹窗

* chore: 自定义基建使用绝对路径

* perf: 简化命名

* fix: 遗漏的issue rp

* fix: 基建

* perf: config.new

* feat: 去除本地作业前缀

* chore: 怎么改名了

* rft: rename

添加一个Dir后缀, 直接using static

* fix: remove不再使用的../../../resrouce

* perf: 短点

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: uye <99072975+ABA2396@users.noreply.github.com>
Co-authored-by: Status102 <102887808+status102@users.noreply.github.com>
2025-09-12 21:25:33 +08:00

280 lines
8.8 KiB
C#

// <copyright file="AnnouncementViewModel.cs" company="MaaAssistantArknights">
// Part of the MaaWpfGui project, maintained by the MaaAssistantArknights team (Maa Team)
// Copyright (C) 2021-2025 MaaAssistantArknights Contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License v3.0 only 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
// </copyright>
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using HandyControl.Controls;
using HandyControl.Tools.Command;
using MaaWpfGui.Configuration.Factory;
using MaaWpfGui.Constants;
using MaaWpfGui.Helper;
using Serilog;
using Stylet;
namespace MaaWpfGui.ViewModels.UI
{
/// <summary>
/// The view model of version update.
/// </summary>
// 通过 container.Get<AnnouncementViewModel>(); 实例化或获取实例
// ReSharper disable once ClassNeverInstantiated.Global
public class AnnouncementViewModel : Screen
{
private static readonly ILogger _logger = Log.ForContext<AnnouncementViewModel>();
private static readonly object _lock = new();
public string ImageSource { get; set; }
public class AnnouncementSection
{
public string Title { get; set; }
public string Content { get; set; }
}
public AnnouncementViewModel()
{
UpdateImageSource();
UpdateScrollStateCommand = new RelayCommand<ScrollViewer>(scrollViewer =>
{
if (scrollViewer == null)
{
return;
}
// 计算是否滚动到底部
IsScrolledToBottom |= scrollViewer.VerticalOffset >= scrollViewer.ScrollableHeight - 10;
});
ScrollToTopCommand = new RelayCommand<ScrollViewer>(scrollViewer =>
{
if (scrollViewer == null)
{
return;
}
scrollViewer.ScrollToTop();
});
AnnouncementSections = [.. ParseAnnouncementInfo(AnnouncementInfo)];
}
private void UpdateImageSource()
{
ImageSource = SettingsViewModel.GuiSettings.Language switch
{
"zh-cn" or "zh-tw" => "/Res/Img/NoSkland.jpg",
_ => "/Res/Img/NoSkLandEn.jpg",
};
}
private static ObservableCollection<AnnouncementSection> ParseAnnouncementInfo(string markdown)
{
var sections = markdown.Split(["### "], StringSplitOptions.RemoveEmptyEntries)
.Select(section =>
{
var lines = section.Split('\n');
return new AnnouncementSection
{
Title = lines.FirstOrDefault(),
Content = "### " + string.Join("\n", lines).Trim([' ', '\n', '-']),
};
}).ToList();
sections.Insert(0, new()
{
Title = "ALL~ the Announcements",
Content = markdown,
});
return [.. sections];
}
public ICommand UpdateScrollStateCommand { get; }
public ICommand ScrollToTopCommand { get; }
private bool _isScrolledToBottom;
public bool IsScrolledToBottom
{
get => _isScrolledToBottom;
set => SetAndNotify(ref _isScrolledToBottom, value);
}
private ObservableCollection<AnnouncementSection> _announcementSections;
public ObservableCollection<AnnouncementSection> AnnouncementSections
{
get => _announcementSections;
set
{
SetAndNotify(ref _announcementSections, value);
SelectedAnnouncementSection = AnnouncementSections.FirstOrDefault();
}
}
private AnnouncementSection _selectedAnnouncementSection;
public AnnouncementSection SelectedAnnouncementSection
{
get => _selectedAnnouncementSection;
set => SetAndNotify(ref _selectedAnnouncementSection, value);
}
private static readonly string _announcementInFile = SettingsViewModel.GuiSettings.Language switch
{
"zh-cn" or "zh-tw" => Path.Combine(PathsHelper.CacheDir, "announcement.md"),
_ => Path.Combine(PathsHelper.CacheDir, "announcement_en.md"),
};
private static string AnnouncementInFile
{
get
{
if (!File.Exists(_announcementInFile))
{
return null;
}
try
{
lock (_lock)
{
return File.ReadAllText(_announcementInFile);
}
}
catch (Exception e)
{
_logger.Error(e, "Failed to read announcement from file");
}
return null;
}
set
{
try
{
lock (_lock)
{
File.WriteAllText(_announcementInFile, value);
}
}
catch (Exception e)
{
_logger.Error(e, "Failed to write announcement to file");
}
}
}
private string _announcementInfo = AnnouncementInFile ?? string.Empty;
/// <summary>
/// Gets the announcement info.
/// </summary>
// ReSharper disable once MemberCanBePrivate.Global
public string AnnouncementInfo
{
get => _announcementInfo;
private set
{
SetAndNotify(ref _announcementInfo, value);
AnnouncementInFile = value;
AnnouncementSections = [.. ParseAnnouncementInfo(AnnouncementInfo)];
}
}
private bool _doNotRemindThisAnnouncementAgain = ConfigFactory.Root.AnnouncementInfo.DoNotShowAgain;
public bool DoNotRemindThisAnnouncementAgain
{
get => _doNotRemindThisAnnouncementAgain;
set
{
SetAndNotify(ref _doNotRemindThisAnnouncementAgain, value);
ConfigFactory.Root.AnnouncementInfo.DoNotShowAgain = value;
}
}
private bool _doNotShowAnnouncement = ConfigFactory.Root.AnnouncementInfo.DoNotShow;
/// <summary>
/// Gets or sets a value indicating whether to show the update.
/// </summary>
public bool DoNotShowAnnouncement
{
get => _doNotShowAnnouncement;
set
{
SetAndNotify(ref _doNotShowAnnouncement, value);
ConfigFactory.Root.AnnouncementInfo.DoNotShow = value;
}
}
/// <summary>
/// 检查更新
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task CheckAndDownloadAnnouncement()
{
string path = "announcements/wpf";
if (SettingsViewModel.GuiSettings.Language is not ("zh-cn" or "zh-tw"))
{
path += "_en";
}
string url = MaaUrls.MaaApi + path + ".md";
using var response = await ETagCache.FetchResponseWithEtag(url, string.IsNullOrEmpty(AnnouncementInfo));
if (response == null ||
response.StatusCode == System.Net.HttpStatusCode.NotModified ||
response.StatusCode != System.Net.HttpStatusCode.OK)
{
return;
}
var body = await HttpResponseHelper.GetStringAsync(response);
if (!string.IsNullOrEmpty(body) && AnnouncementInfo != body)
{
const string Template =
"----------- OLD -----------\n" +
"{AnnouncementInfo}\n" +
"---------------------------\n\n" +
"=========== NEW ===========\n" +
"{Body}\n" +
"===========================";
_logger.Information(Template,
AnnouncementInfo,
body);
AnnouncementInfo = body;
DoNotRemindThisAnnouncementAgain = false;
}
ETagCache.Set(response, url);
ETagCache.Save();
}
public void Close()
{
RequestClose();
}
}
}