42 Commits
v1.3 ... master

Author SHA1 Message Date
Lucas Bortoli
d80e5bb756 Merge pull request #17 from mshafer1/dev/toast_notifications_plus
toast notifications(+)
2025-02-11 06:53:56 -03:00
mshafer1
e91c329feb focus the close button before first launch and on every show to make title text consistently not selected 2025-01-11 08:57:44 -06:00
mshafer1
d8cbe96875 apply suggestions 2025-01-11 08:42:06 -06:00
mshafer1
4fbc4ca4e4 add support for still playing the Windows alert sound 2025-01-10 21:05:28 -06:00
mshafer1
2add8ad2d6 Make the blue a little closer to the true default 2024-12-23 13:13:35 -06:00
mshafer1
1acd970af6 apply Rider suggestions 2024-12-23 13:12:23 -06:00
mshafer1
52f315e4e3 use an enum 2024-12-23 09:23:15 -06:00
mshafer1
582eef1b9d massage the colors a little more 2024-12-21 15:27:32 -06:00
mshafer1
1b51a06eb6 change default of new notifications to light mode 2024-12-21 15:27:22 -06:00
mshafer1
5f57898f9b use seconds for value of timeout rather then mS 2024-12-21 09:53:57 -06:00
mshafer1
025a7d04f8 use 0 for "don't timeout" and don't allow -1 2024-12-21 09:45:08 -06:00
mshafer1
c448de2c17 remove unused imports 2024-12-21 09:40:03 -06:00
mshafer1
f187cd8ea6 introduce theme files and handle in notification popup 2024-12-21 09:39:43 -06:00
mshafer1
7a5d067343 fix button hover behavior 2024-12-21 09:39:04 -06:00
mshafer1
1ae481b434 rename elements in new dialogue to PerlCase per PR comment 2024-12-21 08:42:24 -06:00
mshafer1
c4e5a9059d rename new elements to be camelCase to match existing 2024-12-21 08:40:17 -06:00
mshafer1
ed13382e46 format 2024-12-21 08:32:09 -06:00
mshafer1
5977501b91 remove _icon 2024-12-21 08:28:46 -06:00
mshafer1
859a8938c0 remove IsVisible and just use Visible instead 2024-12-21 08:22:47 -06:00
mshafer1
f8dacac5a6 just utilize the progress bar directly 2024-12-21 08:21:18 -06:00
mshafer1
5b456c14a7 utilize modern c# features 2024-12-21 08:18:57 -06:00
mshafer1
fc3275442c more CamelCasing 2024-12-21 08:18:35 -06:00
mshafer1
be0db848dc improve comment on close handler 2024-12-21 08:11:57 -06:00
mshafer1
2cf458beae use CamelCasing 2024-12-21 08:11:45 -06:00
mshafer1
fa455baa57 add blank lines for readability 2024-12-21 08:03:23 -06:00
mshafer1
934edb79eb use pep8 style private naming and remove this. 2024-12-21 07:51:57 -06:00
mshafer1
8203e6c112 handle if option to show progress bar is off 2024-12-20 21:47:06 -06:00
mshafer1
5414aa4996 pass new options down to custom notifications form 2024-12-20 21:46:45 -06:00
mshafer1
42421d9820 if settings says to use the native notifications, use them 2024-12-20 21:34:18 -06:00
mshafer1
93ac99dd7c add to options 2024-12-20 21:31:40 -06:00
mshafer1
7c5bb6f862 don't show notification window in task bar 2024-12-20 17:47:56 -06:00
mshafer1
65f27c7c42 add a progress bar and timeout text 2024-12-20 17:08:53 -06:00
mshafer1
f908e9c4b0 allow a timeout of -1 in settings 2024-12-20 16:16:57 -06:00
mshafer1
9b902f1d38 use a rich text box to get scroll bar if needed and make window a little smaller 2024-12-20 16:01:15 -06:00
mshafer1
a8af6ce48e if already visible, hide then show
ideally, we would stack multiple up each with its own timeout and close event. For now, this just makes a pop up for the new message.
2024-12-20 15:53:22 -06:00
mshafer1
c8ba64f5d8 remove unused imports 2024-12-20 15:49:42 -06:00
mshafer1
8c04e82dac rearrange file to properties, public, protected, private 2024-12-20 15:49:16 -06:00
mshafer1
8ec1a89941 create a constants class to manage flag names 2024-12-20 15:46:08 -06:00
mshafer1
e24ef645ac implement icon 2024-12-20 13:32:19 -06:00
mshafer1
f5298e1a4e get timeout working 2024-12-20 12:11:07 -06:00
mshafer1
3e43002b47 work on appearance of notification 2024-12-20 12:10:12 -06:00
mshafer1
a7dcf3611c make tray pop up and make it work 2024-12-20 11:29:22 -06:00
11 changed files with 833 additions and 16 deletions

View File

@@ -16,6 +16,7 @@ namespace ntfysh_client
private readonly NotificationListener _notificationListener;
private bool _startInTray;
private bool _trueExit;
private NotificationDialog _notificationDialog;
public MainForm(NotificationListener notificationListener, bool startInTray = false)
{
@@ -32,6 +33,8 @@ namespace ntfysh_client
{
LoadSettings();
LoadTopics();
_notificationDialog = new NotificationDialog();
}
protected override void SetVisibleCore(bool value)
@@ -70,7 +73,23 @@ namespace ntfysh_client
string finalTitle = string.IsNullOrWhiteSpace(e.Title) ? $"{e.Sender.TopicId}@{e.Sender.ServerUrl}" : e.Title;
notifyIcon.ShowBalloonTip((int)TimeSpan.FromSeconds((double)Program.Settings.Timeout).TotalMilliseconds, finalTitle, e.Message, priorityIcon);
if (Program.Settings.NotificationsMethod == SettingsModel.NotificationsType.NativeWindows)
{
notifyIcon.ShowBalloonTip((int)TimeSpan.FromSeconds((double)Program.Settings.Timeout).TotalMilliseconds, finalTitle, e.Message, priorityIcon);
}
else
{
_notificationDialog.ShowNotification(
title: finalTitle,
message: e.Message,
timeoutSeconds: (int)Program.Settings.Timeout,
icon: priorityIcon,
showTimeOutBar: Program.Settings.CustomTrayNotificationsShowTimeoutBar,
showInDarkMode: Program.Settings.CustomTrayNotificationsShowInDarkMode,
playNotificationSound: Program.Settings.CustomTrayNotificationsPlayDefaultWindowsSound
);
}
}
private void OnConnectionMultiAttemptFailure(NotificationListener sender, SubscribedTopic topic)
@@ -132,9 +151,14 @@ namespace ntfysh_client
using SettingsDialog dialog = new();
//Load current settings into dialog
dialog.Timeout = Program.Settings.Timeout;
dialog.ReconnectAttempts = Program.Settings.ReconnectAttempts;
dialog.ReconnectAttemptDelay = Program.Settings.ReconnectAttemptDelay;
dialog.UseNativeWindowsNotifications = Program.Settings.NotificationsMethod == SettingsModel.NotificationsType.NativeWindows;
dialog.UseCustomTrayNotifications = Program.Settings.NotificationsMethod == SettingsModel.NotificationsType.CustomTray;
dialog.CustomTrayNotificationsShowTimeoutBar = Program.Settings.CustomTrayNotificationsShowTimeoutBar;
dialog.CustomTrayNotificationsShowInDarkMode = Program.Settings.CustomTrayNotificationsShowInDarkMode;
dialog.CustomTrayNotificationsPlayDefaultWindowsSound = Program.Settings.CustomTrayNotificationsPlayDefaultWindowsSound;
dialog.Timeout = Program.Settings.Timeout; // set timeout last so bounds are setup before setting value
//Show dialog
DialogResult result = dialog.ShowDialog();
@@ -146,6 +170,10 @@ namespace ntfysh_client
Program.Settings.Timeout = dialog.Timeout;
Program.Settings.ReconnectAttempts = dialog.ReconnectAttempts;
Program.Settings.ReconnectAttemptDelay = dialog.ReconnectAttemptDelay;
Program.Settings.NotificationsMethod = (dialog.UseNativeWindowsNotifications)? SettingsModel.NotificationsType.NativeWindows : SettingsModel.NotificationsType.CustomTray;
Program.Settings.CustomTrayNotificationsShowTimeoutBar = dialog.CustomTrayNotificationsShowTimeoutBar;
Program.Settings.CustomTrayNotificationsShowInDarkMode = dialog.CustomTrayNotificationsShowInDarkMode;
Program.Settings.CustomTrayNotificationsPlayDefaultWindowsSound = dialog.CustomTrayNotificationsPlayDefaultWindowsSound;
//Save new settings persistently
SaveSettingsToFile();
@@ -298,10 +326,14 @@ namespace ntfysh_client
private SettingsModel GetDefaultSettings() => new()
{
Revision = 1,
Revision = 2,
Timeout = 5,
ReconnectAttempts = 10,
ReconnectAttemptDelay = 3
ReconnectAttemptDelay = 3,
NotificationsMethod = SettingsModel.NotificationsType.NativeWindows,
CustomTrayNotificationsShowTimeoutBar = true,
CustomTrayNotificationsShowInDarkMode = false,
CustomTrayNotificationsPlayDefaultWindowsSound = true,
};
private void MergeSettingsRevisions(SettingsModel older, SettingsModel newer)
@@ -313,6 +345,15 @@ namespace ntfysh_client
older.ReconnectAttemptDelay = newer.ReconnectAttemptDelay;
}
//Apply settings introduced in Revision 2 (Native vs custom notifications)
if (older.Revision < 2)
{
older.NotificationsMethod = newer.NotificationsMethod;
older.CustomTrayNotificationsShowTimeoutBar = newer.CustomTrayNotificationsShowTimeoutBar;
older.CustomTrayNotificationsShowInDarkMode = newer.CustomTrayNotificationsShowInDarkMode;
older.CustomTrayNotificationsPlayDefaultWindowsSound = newer.CustomTrayNotificationsPlayDefaultWindowsSound;
}
//Update the revision
older.Revision = newer.Revision;
}
@@ -361,7 +402,7 @@ namespace ntfysh_client
Program.Settings = settings;
//Check the settings revision. If it is older than the current latest revision, apply the settings defaults missing from previous revision
if (Program.Settings.Revision < defaultSettings.ReconnectAttempts)
if (Program.Settings.Revision < defaultSettings.Revision)
{
MergeSettingsRevisions(Program.Settings, defaultSettings);
SaveSettingsToFile();

View File

@@ -0,0 +1,146 @@
namespace ntfysh_client
{
partial class NotificationDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
TxBTitle = new System.Windows.Forms.TextBox();
ButtonClose = new System.Windows.Forms.Button();
TxBMessage = new System.Windows.Forms.RichTextBox();
IconBox = new System.Windows.Forms.PictureBox();
ProgressBar1 = new System.Windows.Forms.ProgressBar();
LblTimeout = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)IconBox).BeginInit();
SuspendLayout();
//
// TxBTitle
//
TxBTitle.BackColor = System.Drawing.SystemColors.ControlDark;
TxBTitle.BorderStyle = System.Windows.Forms.BorderStyle.None;
TxBTitle.Font = new System.Drawing.Font("Segoe UI", 18F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
TxBTitle.ForeColor = System.Drawing.SystemColors.ControlLightLight;
TxBTitle.Location = new System.Drawing.Point(54, 13);
TxBTitle.Name = "TxBTitle";
TxBTitle.ReadOnly = true;
TxBTitle.Size = new System.Drawing.Size(683, 32);
TxBTitle.TabIndex = 0;
TxBTitle.MouseDown += window_MouseDown;
//
// ButtonClose
//
ButtonClose.BackColor = System.Drawing.SystemColors.ActiveCaptionText;
ButtonClose.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
ButtonClose.FlatAppearance.BorderSize = 0;
ButtonClose.FlatAppearance.MouseOverBackColor = System.Drawing.Color.DimGray;
ButtonClose.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
ButtonClose.ForeColor = System.Drawing.SystemColors.ButtonFace;
ButtonClose.Location = new System.Drawing.Point(759, 7);
ButtonClose.Name = "ButtonClose";
ButtonClose.Size = new System.Drawing.Size(29, 38);
ButtonClose.TabIndex = 1;
ButtonClose.Text = "X";
ButtonClose.UseVisualStyleBackColor = false;
ButtonClose.Click += ButtonClose_ClickHandler;
//
// TxBMessage
//
TxBMessage.BackColor = System.Drawing.SystemColors.ControlDark;
TxBMessage.BorderStyle = System.Windows.Forms.BorderStyle.None;
TxBMessage.Font = new System.Drawing.Font("Segoe UI", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
TxBMessage.ForeColor = System.Drawing.SystemColors.ControlLightLight;
TxBMessage.Location = new System.Drawing.Point(12, 57);
TxBMessage.Name = "TxBMessage";
TxBMessage.ReadOnly = true;
TxBMessage.Size = new System.Drawing.Size(776, 191);
TxBMessage.TabIndex = 2;
TxBMessage.Text = "";
TxBMessage.MouseDown += window_MouseDown;
//
// IconBox
//
IconBox.Location = new System.Drawing.Point(12, 12);
IconBox.Name = "IconBox";
IconBox.Size = new System.Drawing.Size(36, 39);
IconBox.TabIndex = 3;
IconBox.TabStop = false;
//
// ProgressBar1
//
ProgressBar1.BackColor = System.Drawing.SystemColors.ControlDarkDark;
ProgressBar1.Enabled = false;
ProgressBar1.ForeColor = System.Drawing.SystemColors.WindowFrame;
ProgressBar1.Location = new System.Drawing.Point(70, 254);
ProgressBar1.MarqueeAnimationSpeed = 1;
ProgressBar1.Name = "ProgressBar1";
ProgressBar1.Size = new System.Drawing.Size(718, 23);
ProgressBar1.Step = 1;
ProgressBar1.Style = System.Windows.Forms.ProgressBarStyle.Continuous;
ProgressBar1.TabIndex = 4;
ProgressBar1.Value = 100;
//
// LblTimeout
//
LblTimeout.AutoSize = true;
LblTimeout.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
LblTimeout.Location = new System.Drawing.Point(21, 254);
LblTimeout.Name = "LblTimeout";
LblTimeout.Size = new System.Drawing.Size(43, 17);
LblTimeout.TabIndex = 5;
LblTimeout.Text = "label1";
LblTimeout.TextAlign = System.Drawing.ContentAlignment.TopRight;
//
// NotificationDialog
//
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
BackColor = System.Drawing.SystemColors.ControlDark;
ClientSize = new System.Drawing.Size(800, 289);
Controls.Add(LblTimeout);
Controls.Add(ProgressBar1);
Controls.Add(IconBox);
Controls.Add(TxBMessage);
Controls.Add(ButtonClose);
Controls.Add(TxBTitle);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
Name = "NotificationDialog";
Text = "NotificationDialog";
Click += window_MouseDown;
((System.ComponentModel.ISupportInitialize)IconBox).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox TxBTitle;
private System.Windows.Forms.Button ButtonClose;
private System.Windows.Forms.RichTextBox TxBMessage;
private System.Windows.Forms.PictureBox IconBox;
private System.Windows.Forms.ProgressBar ProgressBar1;
private System.Windows.Forms.Label LblTimeout;
}
}

View File

@@ -0,0 +1,295 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Diagnostics;
using ntfysh_client.Themes;
using Microsoft.Win32;
using System.Media;
namespace ntfysh_client
{
public partial class NotificationDialog : Form
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool AnimateWindow(IntPtr hWnd, int time, int flags);
private const int ScreenMargin = 20;
private int _timeoutSeconds = 0;
private System.Timers.Timer? _displayTimeoutTimer = null;
private System.Windows.Forms.Timer? _updateTimer = null;
private Stopwatch? _shownStopwatch = null;
private readonly BaseTheme _darkModeTheme = new DarkModeTheme();
private readonly BaseTheme _defaultTheme = new DefaultTheme();
private BaseTheme? _theme = null;
public NotificationDialog()
{
ShowInTaskbar = false;
Visible = false;
TopMost = true;
InitializeComponent();
InitializeWindowHidden();
}
public void ShowNotification(string title, string message, int timeoutSeconds = 0, ToolTipIcon? icon = null, bool showTimeOutBar = true, bool showInDarkMode = true, bool playNotificationSound = false)
{
if (Visible)
{
// close the current notification
HandleTimeout(null, null);
}
_theme = showInDarkMode ? _darkModeTheme : _defaultTheme;
ApplyTheme();
// setup data
IconBox.Image = (icon is null) ? null : ConvertToolTipIconToImage(icon.Value);
TxBTitle.Text = title;
TxBMessage.Text = message;
// setup timers
if (_displayTimeoutTimer != null)
{
_displayTimeoutTimer.Stop();
_displayTimeoutTimer.Dispose();
}
if (_updateTimer != null)
{
_updateTimer.Stop();
_updateTimer.Dispose();
}
if (timeoutSeconds > 0)
{
_displayTimeoutTimer = new System.Timers.Timer(timeoutSeconds * 1000);
_displayTimeoutTimer.Elapsed += HandleTimeout;
_displayTimeoutTimer.Start();
if (showTimeOutBar)
{
ProgressBar1.Value = 100;
_updateTimer = new System.Windows.Forms.Timer();
_updateTimer.Interval = 100;
_updateTimer.Tick += UpdateProgress;
_updateTimer.Start();
_shownStopwatch = new Stopwatch();
_shownStopwatch.Start();
ProgressBar1.Visible = true;
LblTimeout.Visible = true;
_timeoutSeconds = timeoutSeconds;
}
else
{
ProgressBar1.Visible = false;
LblTimeout.Visible = false;
}
}
else
{
ProgressBar1.Visible = false;
LblTimeout.Visible = false;
}
// ok, show the window
Show();
ButtonClose.Focus(); // make sure the window is focused, not the title box.
SetWindowPosition();
if (playNotificationSound) PlayNotificationSound();
}
private void ApplyTheme()
{
_theme ??= _defaultTheme;
// back colors
BackColor = _theme.BackgroundColor;
TxBTitle.BackColor = _theme.BackgroundColor;
TxBMessage.BackColor = _theme.BackgroundColor;
LblTimeout.BackColor = _theme.BackgroundColor;
ProgressBar1.BackColor = _theme.BackgroundColor;
// this one is not "hiding"
ButtonClose.BackColor = _theme.ControlBackGroundColor;
ButtonClose.ForeColor = _theme.BackgroundColor;
// handle mouse over
ButtonClose.FlatAppearance.MouseOverBackColor = _theme.ControlMouseOverBackgroundColor;
// fore colors
ForeColor = _theme.ForegroundColor;
TxBTitle.ForeColor = _theme.ForegroundColor;
TxBMessage.ForeColor = _theme.ForegroundColor;
LblTimeout.ForeColor = _theme.ForegroundColor;
ProgressBar1.ForeColor = _theme.ForegroundColor;
}
private void UpdateProgress(object? sender, EventArgs e)
{
if (_shownStopwatch is null) return;
ProgressBar1.Value = (_timeoutSeconds - _shownStopwatch.Elapsed.Seconds) * 100 / _timeoutSeconds;
LblTimeout.Text = $@"{_timeoutSeconds - _shownStopwatch.Elapsed.Seconds}";
}
protected override void SetVisibleCore(bool value)
{
SetWindowPosition();
if (value)
{
BringToFront();
AnimateWindow(
Handle,
time: 250,
flags: NFWinUserAnimateWindowConstants.AW_SLIDE | NFWinUserAnimateWindowConstants.AW_VER_NEGATIVE
);
}
base.SetVisibleCore(value);
}
private void SetWindowPosition()
{
var workingTop = Screen.PrimaryScreen.WorkingArea.Height - Height;
Top = workingTop - NotificationDialog.ScreenMargin;
var workingLeft = Screen.PrimaryScreen.WorkingArea.Width - Width;
Left = workingLeft - NotificationDialog.ScreenMargin;
}
private void UIThreadAnimatedHideWindow(object? sender, EventArgs? e)
{
AnimateWindow(
Handle,
time: 250,
flags: NFWinUserAnimateWindowConstants.AW_SLIDE | NFWinUserAnimateWindowConstants.AW_VER_POSITIVE | NFWinUserAnimateWindowConstants.AW_HIDE
);
Visible = false;
}
private void HandleTimeout(object? sender, EventArgs? e)
{
CancelTimer();
if (InvokeRequired)
{
// on a background thread, so invoke on the UI thread
Invoke(new Action(() => UIThreadAnimatedHideWindow(sender, e)));
}
else
{
// in the UI thread, invoke directly
UIThreadAnimatedHideWindow(sender, e);
}
}
private Image? ConvertToolTipIconToImage(ToolTipIcon icon) => icon switch
{
ToolTipIcon.Info => SystemIcons.Information.ToBitmap(),
ToolTipIcon.Warning => SystemIcons.Warning.ToBitmap(),
ToolTipIcon.Error => SystemIcons.Error.ToBitmap(),
_ => null
};
private void InitializeWindowHidden()
{
Opacity = 0;
ShowNotification("Title", "Message");
ButtonClose.Focus();
Visible = false;
Opacity = 1;
}
private void ButtonClose_ClickHandler(object sender, EventArgs e)
{
// don't animate, immediately "close"
Visible = false;
}
private class NFWinUserAnimateWindowConstants
{
public const int AW_HOR_POSITIVE = 0x00000001;
public const int AW_HOR_NEGATIVE = 0x00000002;
public const int AW_VER_POSITIVE = 0x00000004;
public const int AW_VER_NEGATIVE = 0x00000008;
public const int AW_CENTER = 0x00000010;
public const int AW_HIDE = 0x00010000;
public const int AW_ACTIVATE = 0x00020000;
public const int AW_SLIDE = 0x00040000;
public const int AW_BLEND = 0x00080000;
}
private void window_MouseDown(object sender, EventArgs e) => CancelTimer();
private void CancelTimer()
{
if (InvokeRequired)
{
// on a background thread, so invoke on the UI thread
Invoke(new Action(() =>
{
LblTimeout.Visible = false;
ProgressBar1.Visible = false;
}));
}
else
{
// in the UI thread, invoke directly
LblTimeout.Visible = false;
ProgressBar1.Visible = false;
}
if (_displayTimeoutTimer != null) // check if the timer has already been disposed
{
_displayTimeoutTimer.Stop();
_displayTimeoutTimer.Dispose();
_displayTimeoutTimer = null;
}
if (_updateTimer != null)
{
_updateTimer.Stop();
_updateTimer.Dispose();
_updateTimer = null;
}
if (_shownStopwatch != null)
{
_shownStopwatch.Stop();
_shownStopwatch = null;
}
}
private void PlayNotificationSound()
{
try
{
using RegistryKey? defaultSoundPathKey = Registry.CurrentUser.OpenSubKey(@"AppEvents\Schemes\Apps\.Default\Notification.Default\.Current");
if (defaultSoundPathKey is null) throw new Exception();
if (defaultSoundPathKey.GetValue(null) is not string defaultSoundPath) throw new Exception();
SoundPlayer loadedSound = new(defaultSoundPath);
loadedSound.Play();
}
catch
{
SystemSounds.Beep.Play(); // consolation prize
}
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -38,10 +38,20 @@ namespace ntfysh_client
reconnectAttemptsLabel = new System.Windows.Forms.Label();
reconnectAttemptDelay = new System.Windows.Forms.NumericUpDown();
reconnectAttemptDelayLabel = new System.Windows.Forms.Label();
nativeVersusCustomNotificationsGroupBox = new System.Windows.Forms.GroupBox();
useCustomTrayNotifications = new System.Windows.Forms.RadioButton();
useNativeWindowsNotifications = new System.Windows.Forms.RadioButton();
groupCustomNotificationSettings = new System.Windows.Forms.GroupBox();
customNotificationsPlayWindowsNotificationAudio = new System.Windows.Forms.CheckBox();
customNotificationsShowInDarkMode = new System.Windows.Forms.CheckBox();
customNotificationsShowTimeoutBar = new System.Windows.Forms.CheckBox();
label1 = new System.Windows.Forms.Label();
buttonPanel.SuspendLayout();
((System.ComponentModel.ISupportInitialize)timeout).BeginInit();
((System.ComponentModel.ISupportInitialize)reconnectAttempts).BeginInit();
((System.ComponentModel.ISupportInitialize)reconnectAttemptDelay).BeginInit();
nativeVersusCustomNotificationsGroupBox.SuspendLayout();
groupCustomNotificationSettings.SuspendLayout();
SuspendLayout();
//
// buttonPanel
@@ -50,7 +60,7 @@ namespace ntfysh_client
buttonPanel.Controls.Add(cancelButton);
buttonPanel.Controls.Add(saveButton);
buttonPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
buttonPanel.Location = new System.Drawing.Point(0, 150);
buttonPanel.Location = new System.Drawing.Point(0, 336);
buttonPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
buttonPanel.Name = "buttonPanel";
buttonPanel.Size = new System.Drawing.Size(531, 51);
@@ -82,9 +92,9 @@ namespace ntfysh_client
timeoutLabel.Location = new System.Drawing.Point(13, 9);
timeoutLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
timeoutLabel.Name = "timeoutLabel";
timeoutLabel.Size = new System.Drawing.Size(488, 15);
timeoutLabel.Size = new System.Drawing.Size(401, 15);
timeoutLabel.TabIndex = 3;
timeoutLabel.Text = "Notification Toast Timeout (seconds, may be ignored by OS based on accessibility settings):";
timeoutLabel.Text = "Notification Toast Timeout (seconds, use -1 to require closing notification):";
//
// timeout
//
@@ -108,7 +118,7 @@ namespace ntfysh_client
reconnectAttemptsLabel.Location = new System.Drawing.Point(12, 54);
reconnectAttemptsLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
reconnectAttemptsLabel.Name = "reconnectAttemptsLabel";
reconnectAttemptsLabel.Size = new System.Drawing.Size(198, 15);
reconnectAttemptsLabel.Size = new System.Drawing.Size(287, 15);
reconnectAttemptsLabel.TabIndex = 5;
reconnectAttemptsLabel.Text = "Maximum reconnect retry attempts (requires restart):";
//
@@ -126,16 +136,102 @@ namespace ntfysh_client
reconnectAttemptDelayLabel.Location = new System.Drawing.Point(12, 99);
reconnectAttemptDelayLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
reconnectAttemptDelayLabel.Name = "reconnectAttemptDelayLabel";
reconnectAttemptDelayLabel.Size = new System.Drawing.Size(191, 15);
reconnectAttemptDelayLabel.Size = new System.Drawing.Size(275, 15);
reconnectAttemptDelayLabel.TabIndex = 7;
reconnectAttemptDelayLabel.Text = "Delay between attempts (seconds, requires restart):";
//
// nativeVersusCustomNotificationsGroupBox
//
nativeVersusCustomNotificationsGroupBox.Controls.Add(useCustomTrayNotifications);
nativeVersusCustomNotificationsGroupBox.Controls.Add(useNativeWindowsNotifications);
nativeVersusCustomNotificationsGroupBox.Location = new System.Drawing.Point(12, 147);
nativeVersusCustomNotificationsGroupBox.Name = "nativeVersusCustomNotificationsGroupBox";
nativeVersusCustomNotificationsGroupBox.Size = new System.Drawing.Size(506, 67);
nativeVersusCustomNotificationsGroupBox.TabIndex = 9;
nativeVersusCustomNotificationsGroupBox.TabStop = false;
//
// useCustomTrayNotifications
//
useCustomTrayNotifications.AutoSize = true;
useCustomTrayNotifications.Location = new System.Drawing.Point(6, 40);
useCustomTrayNotifications.Name = "useCustomTrayNotifications";
useCustomTrayNotifications.Size = new System.Drawing.Size(267, 19);
useCustomTrayNotifications.TabIndex = 1;
useCustomTrayNotifications.TabStop = true;
useCustomTrayNotifications.Text = "Use ntfysh-windows custom tray notifications";
useCustomTrayNotifications.UseVisualStyleBackColor = true;
useCustomTrayNotifications.CheckedChanged += UseCustomTrayNotifications_CheckedChanged;
//
// useNativeWindowsNotifications
//
useNativeWindowsNotifications.AutoSize = true;
useNativeWindowsNotifications.Location = new System.Drawing.Point(6, 15);
useNativeWindowsNotifications.Name = "useNativeWindowsNotifications";
useNativeWindowsNotifications.Size = new System.Drawing.Size(203, 19);
useNativeWindowsNotifications.TabIndex = 0;
useNativeWindowsNotifications.TabStop = true;
useNativeWindowsNotifications.Text = "Use Windows' native notifications";
useNativeWindowsNotifications.UseVisualStyleBackColor = true;
//
// groupCustomNotificationSettings
//
groupCustomNotificationSettings.Controls.Add(customNotificationsPlayWindowsNotificationAudio);
groupCustomNotificationSettings.Controls.Add(customNotificationsShowInDarkMode);
groupCustomNotificationSettings.Controls.Add(customNotificationsShowTimeoutBar);
groupCustomNotificationSettings.Location = new System.Drawing.Point(12, 243);
groupCustomNotificationSettings.Name = "groupCustomNotificationSettings";
groupCustomNotificationSettings.Size = new System.Drawing.Size(504, 87);
groupCustomNotificationSettings.TabIndex = 10;
groupCustomNotificationSettings.TabStop = false;
//
// customNotificationsPlayWindowsNotificationAudio
//
customNotificationsPlayWindowsNotificationAudio.AutoSize = true;
customNotificationsPlayWindowsNotificationAudio.Location = new System.Drawing.Point(4, 59);
customNotificationsPlayWindowsNotificationAudio.Name = "customNotificationsPlayWindowsNotificationAudio";
customNotificationsPlayWindowsNotificationAudio.Size = new System.Drawing.Size(200, 19);
customNotificationsPlayWindowsNotificationAudio.TabIndex = 2;
customNotificationsPlayWindowsNotificationAudio.Text = "Play Windows notification sound";
customNotificationsPlayWindowsNotificationAudio.UseVisualStyleBackColor = true;
//
// customNotificationsShowInDarkMode
//
customNotificationsShowInDarkMode.AutoSize = true;
customNotificationsShowInDarkMode.Location = new System.Drawing.Point(6, 37);
customNotificationsShowInDarkMode.Name = "customNotificationsShowInDarkMode";
customNotificationsShowInDarkMode.Size = new System.Drawing.Size(197, 19);
customNotificationsShowInDarkMode.TabIndex = 1;
customNotificationsShowInDarkMode.Text = "Show notifications in dark mode";
customNotificationsShowInDarkMode.UseVisualStyleBackColor = true;
//
// customNotificationsShowTimeoutBar
//
customNotificationsShowTimeoutBar.AutoSize = true;
customNotificationsShowTimeoutBar.Location = new System.Drawing.Point(6, 14);
customNotificationsShowTimeoutBar.Name = "customNotificationsShowTimeoutBar";
customNotificationsShowTimeoutBar.Size = new System.Drawing.Size(211, 19);
customNotificationsShowTimeoutBar.TabIndex = 0;
customNotificationsShowTimeoutBar.Text = "Show time-out bar on notifications";
customNotificationsShowTimeoutBar.UseVisualStyleBackColor = true;
//
// label1
//
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(12, 227);
label1.Name = "label1";
label1.Size = new System.Drawing.Size(180, 15);
label1.TabIndex = 11;
label1.Text = "Custom tray notification settings";
//
// SettingsDialog
//
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
BackColor = System.Drawing.Color.White;
ClientSize = new System.Drawing.Size(531, 201);
ClientSize = new System.Drawing.Size(531, 387);
Controls.Add(label1);
Controls.Add(groupCustomNotificationSettings);
Controls.Add(nativeVersusCustomNotificationsGroupBox);
Controls.Add(reconnectAttemptDelay);
Controls.Add(reconnectAttemptDelayLabel);
Controls.Add(reconnectAttempts);
@@ -156,6 +252,10 @@ namespace ntfysh_client
((System.ComponentModel.ISupportInitialize)timeout).EndInit();
((System.ComponentModel.ISupportInitialize)reconnectAttempts).EndInit();
((System.ComponentModel.ISupportInitialize)reconnectAttemptDelay).EndInit();
nativeVersusCustomNotificationsGroupBox.ResumeLayout(false);
nativeVersusCustomNotificationsGroupBox.PerformLayout();
groupCustomNotificationSettings.ResumeLayout(false);
groupCustomNotificationSettings.PerformLayout();
ResumeLayout(false);
PerformLayout();
}
@@ -171,5 +271,13 @@ namespace ntfysh_client
private System.Windows.Forms.Label reconnectAttemptsLabel;
private System.Windows.Forms.NumericUpDown reconnectAttemptDelay;
private System.Windows.Forms.Label reconnectAttemptDelayLabel;
private System.Windows.Forms.GroupBox nativeVersusCustomNotificationsGroupBox;
private System.Windows.Forms.RadioButton useCustomTrayNotifications;
private System.Windows.Forms.RadioButton useNativeWindowsNotifications;
private System.Windows.Forms.GroupBox groupCustomNotificationSettings;
private System.Windows.Forms.CheckBox customNotificationsShowTimeoutBar;
private System.Windows.Forms.CheckBox customNotificationsShowInDarkMode;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.CheckBox customNotificationsPlayWindowsNotificationAudio;
}
}

View File

@@ -1,10 +1,13 @@
using System;
using System.Windows.Forms;
using static ntfysh_client.SettingsModel;
namespace ntfysh_client
{
public partial class SettingsDialog : Form
{
public NotificationsType NotificationsMethod { get; set; }
public decimal Timeout
{
get => timeout.Value;
@@ -23,9 +26,53 @@ namespace ntfysh_client
set => reconnectAttemptDelay.Value = value;
}
#region: Native vs custom notifications options. Because these are in a group box, these are mutualy exclusive.
public bool UseNativeWindowsNotifications
{
get => useNativeWindowsNotifications.Checked;
set
{
useNativeWindowsNotifications.Checked = value;
groupCustomNotificationSettings.Enabled = !value;
NotificationsMethod = (value) ? NotificationsType.NativeWindows : NotificationsType.CustomTray;
}
}
public bool UseCustomTrayNotifications
{
get => useCustomTrayNotifications.Checked;
set {
useCustomTrayNotifications.Checked = value;
groupCustomNotificationSettings.Enabled = value;
NotificationsMethod = (value) ? NotificationsType.NativeWindows : NotificationsType.CustomTray;
}
}
#endregion
#region: Custom tray notification options
public bool CustomTrayNotificationsShowTimeoutBar
{
get => customNotificationsShowTimeoutBar.Checked;
set => customNotificationsShowTimeoutBar.Checked = value;
}
public bool CustomTrayNotificationsShowInDarkMode
{
get => customNotificationsShowInDarkMode.Checked;
set => customNotificationsShowInDarkMode.Checked = value;
}
public bool CustomTrayNotificationsPlayDefaultWindowsSound
{
get => customNotificationsPlayWindowsNotificationAudio.Checked;
set => customNotificationsPlayWindowsNotificationAudio.Checked = value;
}
#endregion
public SettingsDialog()
{
InitializeComponent();
SetNotificationsUiElements();
}
private void saveButton_Click(object sender, EventArgs e)
@@ -37,5 +84,19 @@ namespace ntfysh_client
{
DialogResult = DialogResult.Cancel;
}
private void SetNotificationsUiElements()
{
groupCustomNotificationSettings.Enabled = useCustomTrayNotifications.Checked;
timeoutLabel.Text = useCustomTrayNotifications.Checked ? _customNotificationsTimeout : _windowsNotificationsTimeout;
}
private void UseCustomTrayNotifications_CheckedChanged(object sender, EventArgs e)
{
SetNotificationsUiElements();
}
private const string _windowsNotificationsTimeout = "Notification Toast Timeout (seconds, may be ignored by OS based on accessibility settings):";
private const string _customNotificationsTimeout = "Notification Toast Timeout (seconds, use 0 to require closing notification):";
}
}

View File

@@ -2,9 +2,19 @@
{
public class SettingsModel
{
public enum NotificationsType
{
NativeWindows,
CustomTray
}
public uint Revision { get; set; }
public decimal Timeout { get; set; }
public decimal ReconnectAttempts { get; set; }
public decimal ReconnectAttemptDelay { get; set; }
public NotificationsType NotificationsMethod { get; set; }
public bool CustomTrayNotificationsShowTimeoutBar { get; set; }
public bool CustomTrayNotificationsShowInDarkMode { get; set; }
public bool CustomTrayNotificationsPlayDefaultWindowsSound { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System.Drawing;
namespace ntfysh_client.Themes
{
internal abstract class BaseTheme
{
public abstract Color BackgroundColor {get; }
public abstract Color ControlBackGroundColor {get; }
public abstract Color ControlMouseOverBackgroundColor { get; }
public abstract Color ForegroundColor { get; }
}
}

View File

@@ -0,0 +1,12 @@
using System.Drawing;
namespace ntfysh_client.Themes
{
internal class DarkModeTheme: BaseTheme
{
public override Color BackgroundColor { get => SystemColors.ControlDark; }
public override Color ControlBackGroundColor { get => Color.Black; }
public override Color ControlMouseOverBackgroundColor { get => Color.Silver; }
public override Color ForegroundColor { get => SystemColors.WindowText; }
}
}

View File

@@ -0,0 +1,12 @@
using System.Drawing;
namespace ntfysh_client.Themes
{
internal class DefaultTheme: BaseTheme
{
public override Color BackgroundColor { get => Color.White; }
public override Color ControlBackGroundColor { get => SystemColors.ControlDark; }
public override Color ControlMouseOverBackgroundColor { get => Color.CadetBlue; }
public override Color ForegroundColor { get => SystemColors.WindowText; }
}
}