feat(app): 初始化自动关机工具首个版本

实现基于 WPF 的 AutoShutdown 主界面,支持关机、重启、睡眠、休眠、唤醒、锁屏和注销等电源任务。

支持指定时间和倒计时计划、执行前提醒、系统关机撤销、Windows 唤醒任务、托盘运行、自定义图标以及 OmniNotify 通知适配。

修复关闭到托盘时的运行提醒,并支持单击托盘图标打开或收起主界面。

补充 README、发布配置和 win-x64 Release 输出要求。

Release: win-x64
This commit is contained in:
2026-05-18 23:54:58 +08:00
commit d2d81a482b
17 changed files with 1611 additions and 0 deletions

View File

@@ -0,0 +1,220 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using AutoShutdown.Models;
using AutoShutdown.Services;
using WpfBrush = System.Windows.Media.Brush;
using WpfBrushes = System.Windows.Media.Brushes;
using WpfButton = System.Windows.Controls.Button;
using WpfCursors = System.Windows.Input.Cursors;
using WpfFontFamily = System.Windows.Media.FontFamily;
using WpfOrientation = System.Windows.Controls.Orientation;
using WpfRadioButton = System.Windows.Controls.RadioButton;
using WpfTextBox = System.Windows.Controls.TextBox;
namespace AutoShutdown;
internal sealed class NotificationSettingsWindow : Window
{
private readonly NotificationService _notificationService;
private readonly WpfRadioButton _localMode = new() { Content = "软件本身" };
private readonly WpfRadioButton _omniNotifyMode = new() { Content = "OmniNotify" };
private readonly WpfTextBox _endpoint = new();
private readonly WpfTextBox _channel = new();
private readonly TextBlock _status = new();
public NotificationSettings Settings { get; private set; }
public NotificationSettingsWindow(NotificationSettings settings, NotificationService notificationService)
{
_notificationService = notificationService;
Settings = new NotificationSettings
{
Mode = settings.Mode,
OmniNotifyEndpoint = settings.OmniNotifyEndpoint,
Channel = settings.Channel
};
Title = "OmniNotify 适配设置";
Width = 640;
SizeToContent = SizeToContent.Height;
MinWidth = 640;
MaxWidth = 640;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
ResizeMode = ResizeMode.NoResize;
Background = BrushFrom("#F6F8FC");
FontFamily = new WpfFontFamily("Microsoft YaHei UI");
FontSize = 14;
Content = BuildContent();
LoadSettings();
}
private UIElement BuildContent()
{
var host = new Grid
{
Background = BrushFrom("#F6F8FC")
};
var root = new Border
{
Background = BrushFrom("#FFFFFF"),
CornerRadius = new CornerRadius(12),
Padding = new Thickness(26),
Margin = new Thickness(22)
};
var stack = new StackPanel();
root.Child = stack;
stack.Children.Add(new TextBlock
{
Text = "通知方式",
FontSize = 22,
FontWeight = FontWeights.Bold,
Foreground = BrushFrom("#111827")
});
stack.Children.Add(new TextBlock
{
Text = "选择提示由 AutoShutdown 弹窗显示,或发送给本机 OmniNotify。",
Margin = new Thickness(0, 4, 0, 20),
Foreground = BrushFrom("#64748B"),
TextWrapping = TextWrapping.Wrap
});
stack.Children.Add(MakeLabel("提示模式"));
var modeRow = new StackPanel { Orientation = WpfOrientation.Horizontal, Margin = new Thickness(0, 8, 0, 18) };
StyleRadio(_localMode);
StyleRadio(_omniNotifyMode);
_localMode.Margin = new Thickness(0, 0, 22, 0);
modeRow.Children.Add(_localMode);
modeRow.Children.Add(_omniNotifyMode);
stack.Children.Add(modeRow);
stack.Children.Add(MakeLabel("监听地址"));
StyleInput(_endpoint, 536);
_endpoint.Margin = new Thickness(0, 8, 0, 14);
stack.Children.Add(_endpoint);
stack.Children.Add(MakeLabel("频道名"));
StyleInput(_channel, 260);
_channel.Margin = new Thickness(0, 8, 0, 12);
stack.Children.Add(_channel);
_status.Text = "OmniNotify 模式需要先在 OmniNotify 中创建对应频道。";
_status.Foreground = BrushFrom("#64748B");
_status.TextWrapping = TextWrapping.Wrap;
_status.Margin = new Thickness(0, 0, 0, 20);
stack.Children.Add(_status);
var buttons = new StackPanel
{
Orientation = WpfOrientation.Horizontal,
HorizontalAlignment = System.Windows.HorizontalAlignment.Right
};
var test = MakeButton("测试发送", BrushFrom("#475569"));
test.Click += async (_, _) => await TestAsync();
var cancel = MakeButton("取消", BrushFrom("#64748B"));
cancel.Click += (_, _) => DialogResult = false;
var save = MakeButton("保存", BrushFrom("#2563EB"));
save.Click += (_, _) => SaveAndClose();
buttons.Children.Add(test);
buttons.Children.Add(cancel);
buttons.Children.Add(save);
stack.Children.Add(buttons);
host.Children.Add(root);
return host;
}
private void LoadSettings()
{
_localMode.IsChecked = Settings.Mode == NotificationMode.Local;
_omniNotifyMode.IsChecked = Settings.Mode == NotificationMode.OmniNotify;
_endpoint.Text = Settings.OmniNotifyEndpoint;
_channel.Text = Settings.Channel;
}
private async Task TestAsync()
{
var testSettings = ReadSettings();
_status.Text = "正在发送测试消息...";
_status.Foreground = BrushFrom("#64748B");
var ok = await _notificationService.TestOmniNotifyAsync(testSettings);
_status.Text = ok
? "测试消息已发送。"
: "测试失败,请确认 OmniNotify 正在运行、监听地址正确,并且频道已创建。";
_status.Foreground = ok ? BrushFrom("#008060") : BrushFrom("#DC2626");
}
private void SaveAndClose()
{
Settings = ReadSettings();
DialogResult = true;
}
private NotificationSettings ReadSettings()
{
return new NotificationSettings
{
Mode = _omniNotifyMode.IsChecked == true ? NotificationMode.OmniNotify : NotificationMode.Local,
OmniNotifyEndpoint = string.IsNullOrWhiteSpace(_endpoint.Text) ? "http://127.0.0.1:19845/notify" : _endpoint.Text.Trim(),
Channel = string.IsNullOrWhiteSpace(_channel.Text) ? "default" : _channel.Text.Trim()
};
}
private static TextBlock MakeLabel(string text)
{
return new TextBlock
{
Text = text,
FontWeight = FontWeights.SemiBold,
Foreground = BrushFrom("#111827")
};
}
private static void StyleRadio(WpfRadioButton radio)
{
radio.Foreground = BrushFrom("#111827");
radio.FontSize = 14;
radio.Cursor = WpfCursors.Hand;
}
private static void StyleInput(WpfTextBox input, double width)
{
input.Height = 34;
input.Width = width;
input.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
input.Padding = new Thickness(10, 5, 10, 5);
input.BorderBrush = BrushFrom("#CBD5E1");
input.BorderThickness = new Thickness(1);
input.Background = WpfBrushes.White;
input.Foreground = BrushFrom("#111827");
}
private static WpfButton MakeButton(string text, WpfBrush background)
{
var button = new WpfButton
{
Content = text,
Height = 38,
MinWidth = 92,
Margin = new Thickness(10, 0, 0, 0),
Padding = new Thickness(16, 0, 16, 0),
Background = background,
Foreground = WpfBrushes.White,
BorderThickness = new Thickness(0),
FontWeight = FontWeights.SemiBold,
Cursor = WpfCursors.Hand
};
return button;
}
private static WpfBrush BrushFrom(string hex)
{
return (WpfBrush)new BrushConverter().ConvertFromString(hex)!;
}
}